From 47610bb9f4acd94e6d3ed33b24868d43231e5c36 Mon Sep 17 00:00:00 2001 From: dayllenger Date: Sat, 14 Oct 2017 19:14:16 +0300 Subject: [PATCH 1/7] small code edits --- src/dlangui/widgets/editors.d | 44 ++++++++++++++--------------------- src/dlangui/widgets/tabs.d | 18 +++++++++++++- src/dlangui/widgets/widget.d | 14 +++++------ 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index 6a25d80b..8da2b104 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -410,7 +410,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction foreach(ref ch; s) ch = '9'; FontRef fnt = font; - Point sz = fnt.textSize(s); + Point sz = fnt.textSize(cast(immutable)s); _lineNumbersWidth = sz.x; } _leftPaneWidth = _lineNumbersWidth + _modificationMarksWidth + _foldingWidth + _iconsWidth; @@ -765,7 +765,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction return _wantTabs; } - /// sets tab size (in number of spaces) + /// ditto @property EditWidgetBase wantTabs(bool wantTabs) { _wantTabs = wantTabs; return this; @@ -1884,7 +1884,6 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [event.text]); _content.performOperation(op, this); } - if (focused) startCaretBlinking(); return true; } } @@ -1894,9 +1893,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction //if (event.keyCode == KeyCode.RETURN && !readOnly && !_content.multiline) { // return true; //} - bool res = super.onKeyEvent(event); - //if (focused) startCaretBlinking(); - return res; + return super.onKeyEvent(event); } /// Handle Ctrl + Left mouse click on text @@ -2166,6 +2163,9 @@ class EditLine : EditWidgetBase { /// measure override void measure(int parentWidth, int parentHeight) { + if (visibility == Visibility.Gone) + return; + updateFontProps(); measureVisibleText(); measureTextToSetWidgetSize(); @@ -2242,11 +2242,9 @@ class EditLine : EditWidgetBase { // line inside selection Rect startrc = textPosToClient(_selectionRange.start); Rect endrc = textPosToClient(_selectionRange.end); - int startx = startrc.left + _clientRect.left; - int endx = endrc.left + _clientRect.left; Rect rc = lineRect; - rc.left = startx; - rc.right = endx; + rc.left = startrc.left + _clientRect.left; + rc.right = endrc.left + _clientRect.left; if (!rc.empty) { // draw selection rect for line buf.fillRect(rc, focused ? _selectionColorFocused : _selectionColorNormal); @@ -2269,17 +2267,11 @@ class EditLine : EditWidgetBase { applyMargins(rc); applyPadding(rc); auto saver = ClipRectSaver(buf, rc, alpha); + FontRef font = font(); dstring txt = applyPasswordChar(text); - Point sz = font.textSize(txt); - //applyAlign(rc, sz); - Rect lineRect = _clientRect; - lineRect.left = _clientRect.left - _scrollPos.x; - lineRect.right = lineRect.left + calcLineWidth(txt); - Rect visibleRect = lineRect; - visibleRect.left = _clientRect.left; - visibleRect.right = _clientRect.right; - drawLineBackground(buf, lineRect, visibleRect); + + drawLineBackground(buf, _clientRect, _clientRect); font.drawText(buf, rc.left - _scrollPos.x, rc.top, txt, textColor, tabSize); drawCaret(buf); @@ -2366,9 +2358,9 @@ class EditBox : EditWidgetBase { /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). override void layout(Rect rc) { - if (visibility == Visibility.Gone) { + if (visibility == Visibility.Gone) return; - } + if (rc != _pos) _contentChanged = true; Rect contentRc = rc; @@ -2928,9 +2920,9 @@ class EditBox : EditWidgetBase { /// measure override void measure(int parentWidth, int parentHeight) { - if (visibility == Visibility.Gone) { + if (visibility == Visibility.Gone) return; - } + updateFontProps(); updateMaxLineWidth(); int findPanelHeight; @@ -3341,7 +3333,7 @@ class EditBox : EditWidgetBase { FontRef font = font(); for (int i = 0; i < _visibleLines.length; i++) { dstring txt = _visibleLines[i]; - Rect lineRect = rc; + Rect lineRect; lineRect.left = _clientRect.left - _scrollPos.x; lineRect.right = lineRect.left + calcLineWidth(_content[_firstVisibleLine + i]); lineRect.top = _clientRect.top + i * _lineHeight; @@ -3558,9 +3550,9 @@ class LogWidget : EditBox { /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). override void layout(Rect rc) { - if (visibility == Visibility.Gone) { + if (visibility == Visibility.Gone) return; - } + super.layout(rc); if (_scrollLock) { measureVisibleText(); diff --git a/src/dlangui/widgets/tabs.d b/src/dlangui/widgets/tabs.d index 4ff73e09..c0d020bd 100644 --- a/src/dlangui/widgets/tabs.d +++ b/src/dlangui/widgets/tabs.d @@ -53,6 +53,7 @@ class TabItem { private UIString _label; private UIString _tooltipText; private long _lastAccessTs; + this(string id, string labelRes, string iconRes = null, dstring tooltipText = null) { _id = id; _label.id = labelRes; @@ -73,6 +74,7 @@ class TabItem { _lastAccessTs = _lastAccessCounter++; _tooltipText = UIString.fromRaw(tooltipText); } + @property string iconId() const { return _iconRes; } @property string id() const { return _id; } @property ref UIString text() { return _label; } @@ -83,6 +85,7 @@ class TabItem { void updateAccessTs() { _lastAccessTs = _lastAccessCounter++; //std.datetime.Clock.currStdTime; } + /// tooltip text @property dstring tooltipText() { if (_tooltipText.empty) @@ -123,6 +126,7 @@ class TabItemWidget : HorizontalLayout { Signal!TabCloseHandler tabClose; @property TabItem tabItem() { return _item; } @property TabControl tabControl() { return cast(TabControl)parent; } + this(TabItem item, bool enableCloseButton = true) { styleId = STYLE_TAB_UP_BUTTON; _enableCloseButton = enableCloseButton; @@ -153,6 +157,7 @@ class TabItemWidget : HorizontalLayout { if (_closeButton) _closeButton.tooltipText = _item.tooltipText; } + /// tooltip text - when not empty, widget will show tooltips automatically; for advanced tooltips - override hasTooltip and createTooltip override @property dstring tooltipText() { return _item.tooltipText; } /// tooltip text - when not empty, widget will show tooltips automatically; for advanced tooltips - override hasTooltip and createTooltip @@ -180,10 +185,12 @@ class TabItemWidget : HorizontalLayout { styleId = tabButtonStyle; _label.styleId = tabButtonTextStyle; } + override void onDraw(DrawBuf buf) { //debug Log.d("TabWidget.onDraw ", id); super.onDraw(buf); } + protected bool onClick(Widget source) { if (source.compareId("CLOSE")) { Log.d("tab close button pressed"); @@ -192,6 +199,7 @@ class TabItemWidget : HorizontalLayout { } return true; } + @property TabItem item() { return _item; } @@ -334,6 +342,7 @@ class TabControl : WidgetGroupDefaultDrawing { styleId = _tabStyle; addChild(_moreButton); // first child is always MORE button, the rest corresponds to tab list } + void setStyles(string tabStyle, string tabButtonStyle, string tabButtonTextStyle) { _tabStyle = tabStyle; _tabButtonStyle = tabButtonStyle; @@ -533,6 +542,7 @@ class TabControl : WidgetGroupDefaultDrawing { TabItem item = new TabItem(id, label, iconId, tooltipText); return addTab(item, -1, enableCloseButton); } + protected MenuItem getMoreButtonPopupMenu() { if (moreButtonPopupMenu.assigned) { if (auto menu = moreButtonPopupMenu(this)) { @@ -553,6 +563,7 @@ class TabControl : WidgetGroupDefaultDrawing { } return null; } + /// try to invoke popup menu, return true if popup menu is shown protected bool handleMorePopupMenu() { if (auto menu = getMoreButtonPopupMenu()) { @@ -567,6 +578,7 @@ class TabControl : WidgetGroupDefaultDrawing { } return false; } + /// override to handle specific actions override bool handleAction(const Action a) { if (a.id == StandardAction.TabSelectItem) { @@ -575,6 +587,7 @@ class TabControl : WidgetGroupDefaultDrawing { } return super.handleAction(a); } + protected bool onMouse(Widget source, MouseEvent event) { if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { if (source.compareId("MORE")) { @@ -594,6 +607,7 @@ class TabControl : WidgetGroupDefaultDrawing { } return true; } + /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). override void measure(int parentWidth, int parentHeight) { //Log.d("tabControl.measure enter"); @@ -627,6 +641,7 @@ class TabControl : WidgetGroupDefaultDrawing { measuredContent(parentWidth, parentHeight, sz.x, sz.y); //Log.d("tabControl.measure exit"); } + /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). override void layout(Rect rc) { //Log.d("tabControl.layout enter"); @@ -736,7 +751,6 @@ class TabControl : WidgetGroupDefaultDrawing { if (tabChanged.assigned) tabChanged(_selectedTabId, previousSelectedTab); } - } /// container for widgets controlled by TabControl @@ -799,6 +813,7 @@ class TabHost : FrameLayout, TabHandler { requestLayout(); return this; } + /// add new tab by id and label string TabHost addTab(Widget widget, dstring label, string iconId = null, bool enableCloseButton = false, dstring tooltipText = null) { assert(_tabControl !is null, "No TabControl set for TabHost"); @@ -810,6 +825,7 @@ class TabHost : FrameLayout, TabHandler { addChild(widget); return this; } + /// add new tab by id and label string resource id TabHost addTab(Widget widget, string labelResourceId, string iconId = null, bool enableCloseButton = false, dstring tooltipText = null) { assert(_tabControl !is null, "No TabControl set for TabHost"); diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index b8f15bcc..e769f82a 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -366,7 +366,7 @@ public: requestLayout(); return this; } - immutable static int FOCUS_RECT_PADDING = 2; + static enum FOCUS_RECT_PADDING = 2; /// get padding (between background bounds and content of widget) @property Rect padding() const { // get max padding from style padding and background drawable padding @@ -640,16 +640,16 @@ public: /// returns widget visibility (Visible, Invisible, Gone) @property Visibility visibility() { return _visibility; } /// sets widget visibility (Visible, Invisible, Gone) - @property Widget visibility(Visibility visible) { - if (_visibility != visible) { - if ((_visibility == Visibility.Gone) || (visible == Visibility.Gone)) { + @property Widget visibility(Visibility newVisibility) { + if (_visibility != newVisibility) { + if ((_visibility == Visibility.Gone) || (newVisibility == Visibility.Gone)) { if (parent) parent.requestLayout(); else requestLayout(); } else invalidate(); - _visibility = visible; + _visibility = newVisibility; } return this; } @@ -868,9 +868,9 @@ public: this.rect = widget.pos; } static if (BACKEND_GUI) { - static immutable int NEAR_THRESHOLD = 10; + static enum NEAR_THRESHOLD = 10; } else { - static immutable int NEAR_THRESHOLD = 1; + static enum NEAR_THRESHOLD = 1; } bool nearX(TabOrderInfo v) { return v.rect.left >= rect.left - NEAR_THRESHOLD && v.rect.left <= rect.left + NEAR_THRESHOLD; From 1672b644cdc24b9374dcedb79a21107eae2b41b5 Mon Sep 17 00:00:00 2001 From: dayllenger Date: Sat, 14 Oct 2017 19:15:14 +0300 Subject: [PATCH 2/7] optimization: memoize textSize and convertColors functions --- src/dlangui/graphics/fonts.d | 29 ++++++++++++++++++----------- src/dlangui/graphics/glsupport.d | 6 +++++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/dlangui/graphics/fonts.d b/src/dlangui/graphics/fonts.d index 2313ff8c..bbb224e9 100644 --- a/src/dlangui/graphics/fonts.d +++ b/src/dlangui/graphics/fonts.d @@ -74,11 +74,11 @@ enum FontWeight : int { Bold = 800 } -immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad; -immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b; -immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0; -immutable dchar UNICODE_HYPHEN = 0x2010; -immutable dchar UNICODE_NB_HYPHEN = 0x2011; +enum dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad; +enum dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b; +enum dchar UNICODE_NO_BREAK_SPACE = 0x00a0; +enum dchar UNICODE_HYPHEN = 0x2010; +enum dchar UNICODE_NB_HYPHEN = 0x2011; /// custom character properties - for char-by-char drawing of text string with different character color and style struct CustomCharProps { @@ -124,7 +124,7 @@ static if (ENABLE_OPENGL) { } /// constant for measureText maxWidth paramenter - to tell that all characters of text string should be measured. -immutable int MAX_WIDTH_UNSPECIFIED = int.max; +enum int MAX_WIDTH_UNSPECIFIED = int.max; /** Instance of font with specific size, weight, face, etc. * @@ -322,13 +322,20 @@ class Font : RefCountedObject { * tabOffset = when string is drawn not from left position, use to move tab stops left/right * textFlags = TextFlag bit set - to control underline, hotkey label processing, etc... ************************************************************************/ - Point textSize(const dchar[] text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) { - if (_textSizeBuffer.length < text.length + 1) - _textSizeBuffer.length = text.length + 1; - int charsMeasured = measureText(text, _textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags); + Point textSize(dstring text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) { + return textSizeMemoized(this, text, maxWidth, tabSize, tabOffset, textFlags); + } + + import std.functional; + alias textSizeMemoized = memoize!(Font.textSizeImpl); + + static Point textSizeImpl(Font font, const dchar[] text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) { + if (font._textSizeBuffer.length < text.length + 1) + font._textSizeBuffer.length = text.length + 1; + int charsMeasured = font.measureText(text, font._textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags); if (charsMeasured < 1) return Point(0,0); - return Point(_textSizeBuffer[charsMeasured - 1], height); + return Point(font._textSizeBuffer[charsMeasured - 1], font.height); } /***************************************************************************************** diff --git a/src/dlangui/graphics/glsupport.d b/src/dlangui/graphics/glsupport.d index f1a4f387..43c7df76 100644 --- a/src/dlangui/graphics/glsupport.d +++ b/src/dlangui/graphics/glsupport.d @@ -622,7 +622,11 @@ private void FillColor(uint color, Color[] buf_slice) { } } -private float[] convertColors(uint[] cols) pure nothrow { + +import std.functional; +alias convertColors = memoize!(convertColorsImpl); + +float[] convertColorsImpl(uint[] cols) pure nothrow { float[] colors; colors.length = cols.length * 4; foreach(i; 0 .. cols.length) { From bb4c7b0a02b62d2bace677cce6fc0a1f9d014c68 Mon Sep 17 00:00:00 2001 From: gazer Date: Fri, 13 Oct 2017 08:16:11 +0300 Subject: [PATCH 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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;