From e8f11036c9b4722fcd14f0e412be71a61ca463a7 Mon Sep 17 00:00:00 2001 From: gazer Date: Thu, 12 Oct 2017 20:16:48 +0300 Subject: [PATCH 1/5] borders for widgets - initial working implementation --- src/dlangui/graphics/resources.d | 106 ++++++++++++++++++++++--------- src/dlangui/widgets/styles.d | 40 +++++++++--- 2 files changed, 106 insertions(+), 40 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index bd29ea3b..30713bd3 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -334,6 +334,7 @@ class GradientDrawable : Drawable { protected uint _color2; // bottom left protected uint _color3; // top right protected uint _color4; // bottom right + this(uint angle, uint color1, uint color2) { // rotate a gradient; angle goes clockwise import std.math; @@ -370,35 +371,42 @@ class GradientDrawable : Drawable { } } } + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { buf.fillGradientRect(rc, _color1, _color2, _color3, _color4); } + @property override int width() { return 1; } @property override int height() { return 1; } } /// solid borders (may be of different width) and, optionally, solid inner area -class FrameDrawable : Drawable { - protected uint _frameColor; // frame color - protected Rect _frameWidths; // left, top, right, bottom border widths, in pixels +class BorderDrawable : Drawable { + protected uint _borderColor; + protected Rect _borderWidths; // left, top, right, bottom border widths, in pixels protected uint _middleColor; // middle area color (may be transparent) - this(uint frameColor, Rect borderWidths, uint innerAreaColor = 0xFFFFFFFF) { - _frameColor = frameColor; - _frameWidths = borderWidths; + + this(uint borderColor, Rect borderWidths, uint innerAreaColor = 0xFFFFFFFF) { + _borderColor = borderColor; + _borderWidths = borderWidths; _middleColor = innerAreaColor; } - this(uint frameColor, int borderWidth, uint innerAreaColor = 0xFFFFFFFF) { - _frameColor = frameColor; - _frameWidths = Rect(borderWidth, borderWidth, borderWidth, borderWidth); + + this(uint borderColor, int borderWidth, uint innerAreaColor = 0xFFFFFFFF) { + _borderColor = borderColor; + _borderWidths = Rect(borderWidth, borderWidth, borderWidth, borderWidth); _middleColor = innerAreaColor; } + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { - buf.drawFrame(rc, _frameColor, _frameWidths, _middleColor); + buf.drawFrame(rc, _borderColor, _borderWidths, _middleColor); } - @property override int width() { return 1 + _frameWidths.left + _frameWidths.right; } - @property override int height() { return 1 + _frameWidths.top + _frameWidths.bottom; } - @property override Rect padding() { return _frameWidths; } + + @property override int width() { return 1 + _borderWidths.left + _borderWidths.right; } + @property override int height() { return 1 + _borderWidths.top + _borderWidths.bottom; } + @property override Rect padding() { return _borderWidths; } } +deprecated alias FrameDrawable = BorderDrawable; enum DimensionUnits { pixels, @@ -480,18 +488,18 @@ static if (BACKEND_CONSOLE) { } } -/// decode solid color / gradient / frame drawable from string like #AARRGGBB, e.g. #5599AA +/// decode solid color / gradient / border drawable from string like #AARRGGBB, e.g. #5599AA /// /// SolidFillDrawable: #AARRGGBB - e.g. #8090A0 or #80ffffff /// GradientDrawable: #linear,Ndeg,#firstColor,#secondColor -/// FrameDrawable: #frameColor,frameWidth[,#middleColor] -/// or #frameColor,leftBorderWidth,topBorderWidth,rightBorderWidth,bottomBorderWidth[,#middleColor] -/// e.g. #000000,2,#C0FFFFFF - black frame of width 2 with 75% transparent white middle -/// e.g. #0000FF,2,3,4,5,#FFFFFF - blue frame with left,top,right,bottom borders of width 2,3,4,5 and white inner area +/// BorderDrawable: #borderColor,borderWidth[,#middleColor] +/// or #borderColor,leftBorderWidth,topBorderWidth,rightBorderWidth,bottomBorderWidth[,#middleColor] +/// e.g. #000000,2,#C0FFFFFF - black border of width 2 with 75% transparent white middle +/// e.g. #0000FF,2,3,4,5,#FFFFFF - blue border with left,top,right,bottom borders of width 2,3,4,5 and white inner area static Drawable createColorDrawable(string s) { Log.d("creating color drawable ", s); - enum DrawableType { SolidColor, LinearGradient, Frame } + enum DrawableType { SolidColor, LinearGradient, Border } auto type = DrawableType.SolidColor; string[] items = s.split(','); @@ -505,7 +513,7 @@ static Drawable createColorDrawable(string s) { values ~= decodeAngle(item); else { values ~= decodeDimension(item); - type = DrawableType.Frame; + type = DrawableType.Border; } if (i >= 6) break; @@ -515,15 +523,15 @@ static Drawable createColorDrawable(string s) { return new SolidFillDrawable(values[0]); else if (type == DrawableType.LinearGradient && values.length == 3) // angle and two gradient colors return new GradientDrawable(values[0], values[1], values[2]); - else if (type == DrawableType.Frame) { - if (values.length == 2) // frame color and frame width, with transparent inner area - #AARRGGBB,NN - return new FrameDrawable(values[0], values[1]); - else if (values.length == 3) // frame color, frame width, inner area color - #AARRGGBB,NN,#AARRGGBB - return new FrameDrawable(values[0], values[1], values[2]); - else if (values.length == 5) // frame color, frame widths for left,top,right,bottom and transparent inner area - #AARRGGBB,NNleft,NNtop,NNright,NNbottom - return new FrameDrawable(values[0], Rect(values[1], values[2], values[3], values[4])); - else if (values.length == 6) // frame color, frame widths for left,top,right,bottom, inner area color - #AARRGGBB,NNleft,NNtop,NNright,NNbottom,#AARRGGBB - return new FrameDrawable(values[0], Rect(values[1], values[2], values[3], values[4]), values[5]); + else if (type == DrawableType.Border) { + if (values.length == 2) // border color and border width, with transparent inner area - #AARRGGBB,NN + return new BorderDrawable(values[0], values[1]); + else if (values.length == 3) // border color, border width, inner area color - #AARRGGBB,NN,#AARRGGBB + return new BorderDrawable(values[0], values[1], values[2]); + else if (values.length == 5) // border color, border widths for left,top,right,bottom and transparent inner area - #AARRGGBB,NNleft,NNtop,NNright,NNbottom + return new BorderDrawable(values[0], Rect(values[1], values[2], values[3], values[4])); + else if (values.length == 6) // border color, border widths for left,top,right,bottom, inner area color - #AARRGGBB,NNleft,NNtop,NNright,NNbottom,#AARRGGBB + return new BorderDrawable(values[0], Rect(values[1], values[2], values[3], values[4]), values[5]); } Log.e("Invalid drawable string format: ", s); return new EmptyDrawable(); // invalid format - just return empty drawable @@ -1122,6 +1130,44 @@ class StateDrawable : Drawable { } } +/// Drawable which allows to combine together background image, gradient, borders, box shadows, etc. +class CombinedDrawable : Drawable { + + DrawableRef background; + Drawable border; + + this(string backgroundImageId, string borderDescription) { + background = backgroundImageId !is null ? drawableCache.get(backgroundImageId) : new EmptyDrawable; + border = borderDescription !is null ? createColorDrawable(borderDescription) : new EmptyDrawable; + } + + ~this() { + destroy(background); + destroy(border); + background = null; + border = null; + } + + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + // make background image smaller to fit borders + Rect backrc = rc; + backrc.left += border.padding.left; + backrc.top += border.padding.top; + backrc.right -= border.padding.right; + backrc.bottom -= border.padding.bottom; + background.drawTo(buf, backrc, state, tilex0, tiley0); + border.drawTo(buf, rc, state, tilex0, tiley0); + } + + @property override int width() { return background.width + border.padding.left + border.padding.right; } + @property override int height() { return background.height + border.padding.top + border.padding.bottom; } + @property override Rect padding() { + return Rect(background.padding.left + border.padding.left, background.padding.top + border.padding.top, + background.padding.right + border.padding.right, background.padding.bottom + border.padding.bottom); + } +} + + alias DrawableRef = Ref!Drawable; @@ -1336,7 +1382,7 @@ class DrawableCache { if (!_drawable) _error = true; } else if (_filename.startsWith("#")) { - // color reference #AARRGGBB, e.g. #5599AA, or FrameDrawable description string #frameColor,frameSize,#innerColor + // color reference #AARRGGBB, e.g. #5599AA, or a gradient, or BorderDrawable description _drawable = createColorDrawable(_filename); } else if (_filename.startsWith("{")) { // json in {} with text drawable description diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 2cc99952..a96bf9f0 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -324,6 +324,7 @@ protected: uint _alpha; string _fontFace; string _backgroundImageId; + string _border; Rect _padding; Rect _margins; int _minWidth = SIZE_UNSPECIFIED; @@ -411,7 +412,9 @@ public: if (!(cast(Style)this)._backgroundDrawable.isNull) return (cast(Style)this)._backgroundDrawable; string image = backgroundImageId; - if (image !is null) { + if (border !is null) { + (cast(Style)this)._backgroundDrawable = new CombinedDrawable(image, border); + } else if (image !is null) { (cast(Style)this)._backgroundDrawable = drawableCache.get(image); } else { uint color = backgroundColor; @@ -527,6 +530,15 @@ public: return parentStyle.fontSize; } + /// border + @property string border() const { + if (_border !is null) + return _border; + else { + return parentStyle.border; + } + } + //=================================================== // layout parameters: margins / padding @@ -587,7 +599,7 @@ public: return parentStyle.backgroundColor; } - /// font size + /// background image id @property string backgroundImageId() const { if (_backgroundImageId == COLOR_DRAWABLE) return null; @@ -779,6 +791,12 @@ public: return this; } + @property Style border(string s) { + _border = s; + _backgroundDrawable.clear(); + return this; + } + @property Style margins(Rect rc) { _margins = rc; return this; @@ -890,6 +908,7 @@ public: res._alpha = _alpha; res._fontFace = _fontFace; res._backgroundImageId = _backgroundImageId; + res._border = _border; res._padding = _padding; res._margins = _margins; res._minWidth = _minWidth; @@ -1019,6 +1038,10 @@ class Theme : Style { @property override string backgroundImageId() const { return _backgroundImageId; } + /// border + @property override string border() const { + return _border; + } /// minimal width constraint, 0 if limit is not set @property override uint minWidth() const { return _minWidth; @@ -1483,19 +1506,16 @@ bool loadStyleAttributes(Style style, Element elem, bool allowStates) { //Log.d("Theme: loadStyleAttributes ", style.id, " ", elem.tag.attr); if ("backgroundImageId" in elem.tag.attr) style.backgroundImageId = elem.tag.attr["backgroundImageId"]; - if ("backgroundColor" in elem.tag.attr) { - uint col = decodeHexColor(elem.tag.attr["backgroundColor"]); - style.backgroundColor = col; - //Log.d(" background color=", col); - } else { - //Log.d(" no background color attr"); - } + if ("backgroundColor" in elem.tag.attr) + style.backgroundColor = decodeHexColor(elem.tag.attr["backgroundColor"]); if ("textColor" in elem.tag.attr) style.textColor = decodeHexColor(elem.tag.attr["textColor"]); if ("margins" in elem.tag.attr) style.margins = decodeRect(elem.tag.attr["margins"]); if ("padding" in elem.tag.attr) style.padding = decodeRect(elem.tag.attr["padding"]); + if ("border" in elem.tag.attr) + style.border = elem.tag.attr["border"]; if ("align" in elem.tag.attr) style.alignment = decodeAlignment(elem.tag.attr["align"]); if ("minWidth" in elem.tag.attr) @@ -1549,7 +1569,7 @@ bool loadStyleAttributes(Style style, Element elem, bool allowStates) { if (colorid) style.setCustomColor(colorid, color); } else if (item.tag.name.equal("length")) { - // + // string lenid = attrValue(item, "id"); string lenvalue = attrValue(item, "value"); uint len = decodeDimension(lenvalue); From 8a5152c5c24e0059934bce11ef6917276bf66018 Mon Sep 17 00:00:00 2001 From: gazer Date: Thu, 12 Oct 2017 20:56:54 +0300 Subject: [PATCH 2/5] CombinedDrawable - work with solid fill background too --- src/dlangui/graphics/resources.d | 7 +++++-- src/dlangui/widgets/styles.d | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index 30713bd3..6ea86c86 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -1136,8 +1136,11 @@ class CombinedDrawable : Drawable { DrawableRef background; Drawable border; - this(string backgroundImageId, string borderDescription) { - background = backgroundImageId !is null ? drawableCache.get(backgroundImageId) : new EmptyDrawable; + this(uint backgroundColor, string backgroundImageId, string borderDescription) { + background = + (backgroundImageId !is null) ? drawableCache.get(backgroundImageId) : + (!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : + new EmptyDrawable; border = borderDescription !is null ? createColorDrawable(borderDescription) : new EmptyDrawable; } diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index a96bf9f0..31e1af9c 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -412,12 +412,12 @@ public: if (!(cast(Style)this)._backgroundDrawable.isNull) return (cast(Style)this)._backgroundDrawable; string image = backgroundImageId; + uint color = backgroundColor; if (border !is null) { - (cast(Style)this)._backgroundDrawable = new CombinedDrawable(image, border); + (cast(Style)this)._backgroundDrawable = new CombinedDrawable(color, image, border); } else if (image !is null) { (cast(Style)this)._backgroundDrawable = drawableCache.get(image); } else { - uint color = backgroundColor; (cast(Style)this)._backgroundDrawable = isFullyTransparentColor(color) ? new EmptyDrawable() : new SolidFillDrawable(color); } return (cast(Style)this)._backgroundDrawable; From 0f0bbe4f5398f778c2e27d6519f842f05c1e64ef Mon Sep 17 00:00:00 2001 From: gazer Date: Thu, 12 Oct 2017 21:42:34 +0300 Subject: [PATCH 3/5] get borders from drawables' cache --- src/dlangui/graphics/resources.d | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index 6ea86c86..046d00af 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -1134,21 +1134,14 @@ class StateDrawable : Drawable { class CombinedDrawable : Drawable { DrawableRef background; - Drawable border; + DrawableRef border; this(uint backgroundColor, string backgroundImageId, string borderDescription) { background = (backgroundImageId !is null) ? drawableCache.get(backgroundImageId) : (!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : new EmptyDrawable; - border = borderDescription !is null ? createColorDrawable(borderDescription) : new EmptyDrawable; - } - - ~this() { - destroy(background); - destroy(border); - background = null; - border = null; + border = borderDescription !is null ? drawableCache.get(borderDescription) : new EmptyDrawable; } override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { From b8f8f97634bbc424cefa7d3718e62a3663645bcd Mon Sep 17 00:00:00 2001 From: gazer Date: Fri, 13 Oct 2017 07:59:45 +0300 Subject: [PATCH 4/5] sanitize incoming border property --- src/dlangui/widgets/styles.d | 37 +++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 31e1af9c..b074663f 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -1501,6 +1501,32 @@ int decodeLayoutDimension(string s) { return decodeDimension(s); } +/// remove superfluous space characters from a border property +string sanitizeBorderProperty(string s) pure { + string[] parts = s.split(','); + foreach (ref part; parts) + part = part.strip(); + string joined = parts.join(','); + + char[] res; + // replace repeating space characters with one space + import std.ascii : isWhite; + bool isSpace; + foreach (c; joined) { + if (isWhite(c)) { + if (!isSpace) { + res ~= ' '; + isSpace = true; + } + } else { + res ~= c; + isSpace = false; + } + } + + return cast(string)res; +} + /// load style attributes from XML element bool loadStyleAttributes(Style style, Element elem, bool allowStates) { //Log.d("Theme: loadStyleAttributes ", style.id, " ", elem.tag.attr); @@ -1515,7 +1541,7 @@ bool loadStyleAttributes(Style style, Element elem, bool allowStates) { if ("padding" in elem.tag.attr) style.padding = decodeRect(elem.tag.attr["padding"]); if ("border" in elem.tag.attr) - style.border = elem.tag.attr["border"]; + style.border = sanitizeBorderProperty(elem.tag.attr["border"]); if ("align" in elem.tag.attr) style.alignment = decodeAlignment(elem.tag.attr["align"]); if ("minWidth" in elem.tag.attr) @@ -1714,3 +1740,12 @@ string overrideCustomDrawableId(string id) { shared static ~this() { currentTheme = null; } + + + + +unittest { + assert(sanitizeBorderProperty(" #aaa, 2 ") == "#aaa,2"); + assert(sanitizeBorderProperty(" #aaa, 2, 2, 2, 4") == "#aaa,2,2,2,4"); + assert(sanitizeBorderProperty(" #a aa , 2 4 ") == "#a aa,2 4"); +} From ecf2c1791a53b19f6a39bb1a2bb81cc555185364 Mon Sep 17 00:00:00 2001 From: dayllenger Date: Sat, 14 Oct 2017 23:08:06 +0300 Subject: [PATCH 5/5] fix segfault because of _nullDrawable --- src/dlangui/graphics/resources.d | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index 046d00af..9866023a 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -1139,8 +1139,9 @@ class CombinedDrawable : Drawable { this(uint backgroundColor, string backgroundImageId, string borderDescription) { background = (backgroundImageId !is null) ? drawableCache.get(backgroundImageId) : - (!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : - new EmptyDrawable; + (!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : null; + if (background is null) + background = new EmptyDrawable; border = borderDescription !is null ? drawableCache.get(borderDescription) : new EmptyDrawable; }