From bb4c7b0a02b62d2bace677cce6fc0a1f9d014c68 Mon Sep 17 00:00:00 2001 From: gazer Date: Fri, 13 Oct 2017 08:16:11 +0300 Subject: [PATCH 1/5] box shadow property in Style --- src/dlangui/graphics/resources.d | 2 +- src/dlangui/widgets/styles.d | 33 ++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index 9866023a..f3f15739 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -1136,7 +1136,7 @@ class CombinedDrawable : Drawable { DrawableRef background; DrawableRef border; - this(uint backgroundColor, string backgroundImageId, string borderDescription) { + this(uint backgroundColor, string backgroundImageId, string borderDescription, string boxShadowDescription) { background = (backgroundImageId !is null) ? drawableCache.get(backgroundImageId) : (!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : null; diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index b074663f..2e762538 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 _boxShadow; string _border; Rect _padding; Rect _margins; @@ -413,8 +414,10 @@ public: return (cast(Style)this)._backgroundDrawable; string image = backgroundImageId; uint color = backgroundColor; - if (border !is null) { - (cast(Style)this)._backgroundDrawable = new CombinedDrawable(color, image, border); + string borders = border; + string shadows = boxShadow; + if (borders !is null || shadows !is null) { + (cast(Style)this)._backgroundDrawable = new CombinedDrawable(color, image, borders, shadows); } else if (image !is null) { (cast(Style)this)._backgroundDrawable = drawableCache.get(image); } else { @@ -530,6 +533,15 @@ public: return parentStyle.fontSize; } + /// box shadow + @property string boxShadow() const { + if (_boxShadow !is null) + return _boxShadow; + else { + return parentStyle.boxShadow; + } + } + /// border @property string border() const { if (_border !is null) @@ -791,6 +803,12 @@ public: return this; } + @property Style boxShadow(string s) { + _boxShadow = s; + _backgroundDrawable.clear(); + return this; + } + @property Style border(string s) { _border = s; _backgroundDrawable.clear(); @@ -1038,6 +1056,10 @@ class Theme : Style { @property override string backgroundImageId() const { return _backgroundImageId; } + /// box shadow + @property override string boxShadow() const { + return _boxShadow; + } /// border @property override string border() const { return _border; @@ -1527,6 +1549,11 @@ string sanitizeBorderProperty(string s) pure { return cast(string)res; } +/// remove superfluous space characters from a box shadow property +string sanitizeBoxShadowProperty(string s) pure { + return sanitizeBorderProperty(s); +} + /// load style attributes from XML element bool loadStyleAttributes(Style style, Element elem, bool allowStates) { //Log.d("Theme: loadStyleAttributes ", style.id, " ", elem.tag.attr); @@ -1542,6 +1569,8 @@ bool loadStyleAttributes(Style style, Element elem, bool allowStates) { style.padding = decodeRect(elem.tag.attr["padding"]); if ("border" in elem.tag.attr) style.border = sanitizeBorderProperty(elem.tag.attr["border"]); + if ("boxShadow" in elem.tag.attr) + style.boxShadow = sanitizeBoxShadowProperty(elem.tag.attr["boxShadow"]); if ("align" in elem.tag.attr) style.alignment = decodeAlignment(elem.tag.attr["align"]); if ("minWidth" in elem.tag.attr) From fe0e86d58b54854f5923b157e590c5e11d36de45 Mon Sep 17 00:00:00 2001 From: gazer Date: Fri, 13 Oct 2017 08:31:25 +0300 Subject: [PATCH 2/5] add prefix to drawable inner names for borders and box shadows --- src/dlangui/graphics/resources.d | 43 +++++++++++++++++++------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index f3f15739..fc312c5b 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -491,32 +491,39 @@ static if (BACKEND_CONSOLE) { /// 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 -/// 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 +/// GradientDrawable: #linear,Ndeg,firstColor,secondColor +/// BorderDrawable: #border,borderColor,borderWidth[,middleColor] +/// or #border,borderColor,leftBorderWidth,topBorderWidth,rightBorderWidth,bottomBorderWidth[,middleColor] +/// e.g. #border,#000000,2,#C0FFFFFF - black border of width 2 with 75% transparent white middle +/// e.g. #border,#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, Border } + enum DrawableType { SolidColor, LinearGradient, Border, BoxShadow } auto type = DrawableType.SolidColor; string[] items = s.split(','); uint[] values; - foreach (i, item; items) { - if (item == "#linear") + if (items.length != 0) { + if (items[0] == "#linear") type = DrawableType.LinearGradient; - else if (item.startsWith("#")) - values ~= decodeHexColor(item); - else if (item.endsWith("deg")) - values ~= decodeAngle(item); - else { - values ~= decodeDimension(item); + else if (items[0] == "#border") type = DrawableType.Border; + else if (items[0] == "#box-shadow") + type = DrawableType.BoxShadow; + else if (items[0].startsWith("#")) + values ~= decodeHexColor(items[0]); + + foreach (i, item; items[1 .. $]) { + if (item.startsWith("#")) + values ~= decodeHexColor(item); + else if (item.endsWith("deg")) + values ~= decodeAngle(item); + else + values ~= decodeDimension(item); + if (i >= 6) + break; } - if (i >= 6) - break; } if (type == DrawableType.SolidColor && values.length == 1) // only color #AARRGGBB @@ -1133,16 +1140,18 @@ class StateDrawable : Drawable { /// Drawable which allows to combine together background image, gradient, borders, box shadows, etc. class CombinedDrawable : Drawable { + DrawableRef boxShadow; DrawableRef background; DrawableRef border; this(uint backgroundColor, string backgroundImageId, string borderDescription, string boxShadowDescription) { + boxShadow = boxShadowDescription !is null ? drawableCache.get("#box-shadow," ~ boxShadowDescription) : new EmptyDrawable; background = (backgroundImageId !is null) ? drawableCache.get(backgroundImageId) : (!backgroundColor.isFullyTransparentColor) ? new SolidFillDrawable(backgroundColor) : null; if (background is null) background = new EmptyDrawable; - border = borderDescription !is null ? drawableCache.get(borderDescription) : new EmptyDrawable; + border = borderDescription !is null ? drawableCache.get("#border," ~ borderDescription) : new EmptyDrawable; } override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { From 5310d958b7d581fceb187ab57b0826b9b756cbc9 Mon Sep 17 00:00:00 2001 From: gazer Date: Fri, 13 Oct 2017 09:01:25 +0300 Subject: [PATCH 3/5] very basic BoxShadowDrawable --- src/dlangui/graphics/drawbuf.d | 19 +++++++++--- src/dlangui/graphics/resources.d | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index 4a082ff0..f2687849 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -840,6 +840,11 @@ class DrawBuf : RefCountedObject { } } + /// Apply Gaussian blur on the image + void blur(uint blurSize) { + // TODO + } + /// create drawbuf with copy of current buffer with changed colors (returns this if not supported) DrawBuf transformColors(ref ColorTransform transform) { return this; @@ -858,20 +863,26 @@ class DrawBuf : RefCountedObject { alias DrawBufRef = Ref!DrawBuf; -/// RAII setting/restoring of clip rectangle +/// RAII setting/restoring of a DrawBuf clip rectangle struct ClipRectSaver { private DrawBuf _buf; private Rect _oldClipRect; private uint _oldAlpha; - /// apply (intersect) new clip rectangle and alpha to draw buf; restore - this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0) { + + /// apply (intersect) new clip rectangle and alpha to draw buf + /// set `intersect` parameter to `false`, if you want to draw something outside of the widget + this(DrawBuf buf, ref Rect newClipRect, uint newAlpha = 0, bool intersect = true) { _buf = buf; _oldClipRect = buf.clipRect; _oldAlpha = buf.alpha; - buf.intersectClipRect(newClipRect); + if (intersect) + buf.intersectClipRect(newClipRect); + else + buf.clipRect = newClipRect; if (newAlpha) buf.addAlpha(newAlpha); } + /// restore previous clip rectangle ~this() { _buf.clipRect = _oldClipRect; _buf.alpha = _oldAlpha; diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index fc312c5b..3a433f4b 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -408,6 +408,46 @@ class BorderDrawable : Drawable { } deprecated alias FrameDrawable = BorderDrawable; +/// box shadows, can be blurred +class BoxShadowDrawable : Drawable { + protected int _offsetX; + protected int _offsetY; + protected int _blurSize; + protected uint _color; + protected Ref!ColorDrawBuf texture; + + this(int offsetX, int offsetY, uint blurSize = 0, uint color = 0x0) { + _offsetX = offsetX; + _offsetY = offsetY; + _blurSize = blurSize; + _color = color; + // now create a texture which will contain the shadow + uint size = 2 * blurSize + 3; + texture = new ColorDrawBuf(size, size); // TODO: get from/put to cache + // clear + texture.fill(0xFFFFFFFF); + // draw a square in center of the texture + texture.fillRect(Rect(blurSize, blurSize, size - blurSize, size - blurSize), color); + // blur the square + texture.blur(blurSize); + } + + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + // move and expand the shadow + rc.left += _offsetX - _blurSize; + rc.right += _offsetX + _blurSize; + rc.top += _offsetY - _blurSize; + rc.bottom += _offsetY + _blurSize; + // apply new clipping to DrawBuf to draw outside of the widget + auto saver = ClipRectSaver(buf, rc, 0, false); + buf.drawRescaled(rc, texture, Rect(0, 0, texture.width, texture.height)); // TODO: nine-patch + } + + @property override int width() { return 1; } + @property override int height() { return 1; } +} + + enum DimensionUnits { pixels, points, @@ -504,6 +544,7 @@ static Drawable createColorDrawable(string s) { string[] items = s.split(','); uint[] values; + int[] ivalues; if (items.length != 0) { if (items[0] == "#linear") type = DrawableType.LinearGradient; @@ -519,6 +560,8 @@ static Drawable createColorDrawable(string s) { values ~= decodeHexColor(item); else if (item.endsWith("deg")) values ~= decodeAngle(item); + else if (type == DrawableType.BoxShadow) // offsets may be negative + ivalues ~= item.startsWith("-") ? -decodeDimension(item) : decodeDimension(item); else values ~= decodeDimension(item); if (i >= 6) @@ -539,6 +582,13 @@ static Drawable createColorDrawable(string s) { 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]); + } else if (type == DrawableType.BoxShadow) { + if (ivalues.length == 2 && values.length == 0) // shadow X and Y offsets + return new BoxShadowDrawable(ivalues[0], ivalues[1]); + else if (ivalues.length == 3 && values.length == 0) // shadow offsets and blur size + return new BoxShadowDrawable(ivalues[0], ivalues[1], ivalues[2]); + else if (ivalues.length == 3 && values.length == 1) // shadow offsets, blur size and color + return new BoxShadowDrawable(ivalues[0], ivalues[1], ivalues[2], values[0]); } Log.e("Invalid drawable string format: ", s); return new EmptyDrawable(); // invalid format - just return empty drawable @@ -1155,6 +1205,7 @@ class CombinedDrawable : Drawable { } override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + boxShadow.drawTo(buf, rc, state, tilex0, tiley0); // make background image smaller to fit borders Rect backrc = rc; backrc.left += border.padding.left; From 18d30eb8a6cf7c47e38fd0d6b7a588df3524e683 Mon Sep 17 00:00:00 2001 From: dayllenger Date: Fri, 13 Oct 2017 15:04:22 +0300 Subject: [PATCH 4/5] gaussian blur for ColorDrawBuf --- src/dlangui/graphics/drawbuf.d | 76 +++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index f2687849..33024b96 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -840,11 +840,6 @@ class DrawBuf : RefCountedObject { } } - /// Apply Gaussian blur on the image - void blur(uint blurSize) { - // TODO - } - /// create drawbuf with copy of current buffer with changed colors (returns this if not supported) DrawBuf transformColors(ref ColorTransform transform) { return this; @@ -1235,7 +1230,6 @@ class ColorDrawBufBase : DrawBuf { row[x] = blendARGB(row[x], color, alpha); } } - } class GrayDrawBuf : DrawBuf { @@ -1480,6 +1474,7 @@ class GrayDrawBuf : DrawBuf { class ColorDrawBuf : ColorDrawBufBase { uint[] _buf; + /// create ARGB8888 draw buf of specified width and height this(int width, int height) { resize(width, height); @@ -1536,11 +1531,13 @@ class ColorDrawBuf : ColorDrawBufBase { pixel ^= 0xFF000000; } } + override uint * scanLine(int y) { if (y >= 0 && y < _dy) return _buf.ptr + _dx * y; return null; } + override void resize(int width, int height) { if (_dx == width && _dy == height) return; @@ -1549,6 +1546,7 @@ class ColorDrawBuf : ColorDrawBufBase { _buf.length = _dx * _dy; resetClipping(); } + override void fill(uint color) { if (hasClipping) { fillRect(_clipRect, color); @@ -1559,6 +1557,7 @@ class ColorDrawBuf : ColorDrawBufBase { foreach(i; 0 .. len) p[i] = color; } + override DrawBuf transformColors(ref ColorTransform transform) { if (transform.empty) return this; @@ -1583,6 +1582,71 @@ class ColorDrawBuf : ColorDrawBufBase { } return res; } + + /// Apply Gaussian blur on the image + void blur(uint blurSize) { + if (blurSize == 0) + return; // trivial case + + // utility functions to get and set color + float[4] get(uint[] buf, uint x, uint y) { + uint c = buf[x + y * _dx]; + float a = 255 - (c >> 24); + float r = (c >> 16) & 0xFF; + float g = (c >> 8) & 0xFF; + float b = (c >> 0) & 0xFF; + return [r, g, b, a]; + } + void set(uint[] buf, uint x, uint y, float[4] c) { + buf[x + y * _dx] = makeRGBA(c[0], c[1], c[2], 255 - c[3]); + } + + + import std.algorithm : max, min; + import std.math; + + // Gaussian function + float weight(in float x, in float sigma) pure nothrow { + enum inv_sqrt_2pi = 1 / sqrt(2 * PI); + return exp(- x ^^ 2 / (2 * sigma ^^ 2)) * inv_sqrt_2pi / sigma; + } + + void blurOneDimension(uint[] bufIn, uint[] bufOut, uint blurSize, bool horizontally) { + + float sigma = blurSize > 2 ? blurSize / 3.0 : blurSize / 2.0; + + foreach (x; 0 .. _dx) { + foreach (y; 0 .. _dy) { + float[4] c; + c[] = 0; + + float sum = 0; + foreach (int i; 1 .. blurSize + 1) { + float[4] c1 = get(bufIn, + horizontally ? min(x + i, _dx - 1) : x, + horizontally ? y : min(y + i, _dy - 1) + ); + float[4] c2 = get(bufIn, + horizontally ? max(x - i, 0) : x, + horizontally ? y : max(y - i, 0) + ); + float w = weight(i, sigma); + c[] += (c1[] + c2[]) * w; + sum += 2 * w; + } + c[] += get(bufIn, x, y)[] * (1 - sum); + set(bufOut, x, y, c); + } + } + } + // intermediate buffer for image + uint[] tmpbuf; + tmpbuf.length = _buf.length; + // do horizontal blur + blurOneDimension(_buf, tmpbuf, blurSize, true); + // then do vertical blur + blurOneDimension(tmpbuf, _buf, blurSize, false); + } } From 2b74271c310c49f8ca4ddac1b76c79641452ad5e Mon Sep 17 00:00:00 2001 From: dayllenger Date: Mon, 16 Oct 2017 12:16:15 +0300 Subject: [PATCH 5/5] draw box shadows as nine-patch; fix style's child states --- src/dlangui/graphics/resources.d | 41 +++++++++++++++++++++++++------- src/dlangui/widgets/styles.d | 1 + 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/dlangui/graphics/resources.d b/src/dlangui/graphics/resources.d index 3a433f4b..b595aab1 100644 --- a/src/dlangui/graphics/resources.d +++ b/src/dlangui/graphics/resources.d @@ -422,10 +422,10 @@ class BoxShadowDrawable : Drawable { _blurSize = blurSize; _color = color; // now create a texture which will contain the shadow - uint size = 2 * blurSize + 3; + uint size = 4 * blurSize + 1; texture = new ColorDrawBuf(size, size); // TODO: get from/put to cache // clear - texture.fill(0xFFFFFFFF); + texture.fill(color | 0xFF000000); // draw a square in center of the texture texture.fillRect(Rect(blurSize, blurSize, size - blurSize, size - blurSize), color); // blur the square @@ -433,14 +433,39 @@ class BoxShadowDrawable : Drawable { } override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + // this is a size of blurred part + uint b = _blurSize + _blurSize / 2 + 1; // move and expand the shadow - rc.left += _offsetX - _blurSize; - rc.right += _offsetX + _blurSize; - rc.top += _offsetY - _blurSize; - rc.bottom += _offsetY + _blurSize; - // apply new clipping to DrawBuf to draw outside of the widget + rc.left += _offsetX - b; + rc.top += _offsetY - b; + rc.right += _offsetX + b; + rc.bottom += _offsetY + b; + + // apply new clipping to the DrawBuf to draw outside of the widget auto saver = ClipRectSaver(buf, rc, 0, false); - buf.drawRescaled(rc, texture, Rect(0, 0, texture.width, texture.height)); // TODO: nine-patch + + if (_blurSize > 0) { + // Manual nine-patch + uint w = texture.width; + uint h = texture.height; + + buf.drawFragment(rc.left, rc.top, texture, Rect(0, 0, b, b)); // top left + buf.drawRescaled(Rect(rc.left + b, rc.top, rc.right - b, rc.top + b), texture, Rect(b, 0, w - b, b)); // top center + buf.drawFragment(rc.right - b, rc.top, texture, Rect(w - b, 0, w, b)); // top right + + buf.drawRescaled(Rect(rc.left, rc.top + b, rc.left + b, rc.bottom - b), texture, Rect(0, b, b, h - b)); // middle left + buf.drawRescaled(Rect(rc.left + b, rc.top + b, rc.right - b, rc.bottom - b), texture, Rect(b, b, w - b, h - b)); // middle center + buf.drawRescaled(Rect(rc.right - b, rc.top + b, rc.right, rc.bottom - b), texture, Rect(w - b, b, w, h - b)); // middle right + + buf.drawFragment(rc.left, rc.bottom - b, texture, Rect(0, h - b, b, h)); // bottom left + buf.drawRescaled(Rect(rc.left + b, rc.bottom - b, rc.right - b, rc.bottom), texture, Rect(b, h - b, w - b, h)); // bottom center + buf.drawFragment(rc.right - b, rc.bottom - b, texture, Rect(w - b, h - b, w, h)); // bottom right + + // debug + //~ buf.drawFragment(rc.left, rc.top, texture, Rect(0, 0, w, h)); + } else { + buf.fillRect(rc, _color); + } } @property override int width() { return 1; } diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 2e762538..28d907a4 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -926,6 +926,7 @@ public: res._alpha = _alpha; res._fontFace = _fontFace; res._backgroundImageId = _backgroundImageId; + res._boxShadow = _boxShadow; res._border = _border; res._padding = _padding; res._margins = _margins;