From f3c0197837c9ddc1d361f4fa0271c4bd0294be2a Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Thu, 10 Apr 2014 15:29:01 +0400 Subject: [PATCH] state drawables --- examples/example1/res/tab_btn_up.xml | 14 +++ src/dlangui/graphics/drawbuf.d | 142 ++++++++++++++++++++++++++- src/dlangui/widgets/controls.d | 6 +- src/dlangui/widgets/styles.d | 24 +++-- src/dlangui/widgets/widget.d | 8 +- 5 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 examples/example1/res/tab_btn_up.xml diff --git a/examples/example1/res/tab_btn_up.xml b/examples/example1/res/tab_btn_up.xml new file mode 100644 index 00000000..9e16cf29 --- /dev/null +++ b/examples/example1/res/tab_btn_up.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/src/dlangui/graphics/drawbuf.d b/src/dlangui/graphics/drawbuf.d index f8cd4874..abb8db38 100644 --- a/src/dlangui/graphics/drawbuf.d +++ b/src/dlangui/graphics/drawbuf.d @@ -687,14 +687,14 @@ class Drawable : RefCountedObject { ~this() { //Log.d("Destroyed drawable, count=", --_instanceCount); } - abstract void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0); + abstract void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0); @property abstract int width(); @property abstract int height(); @property Rect padding() { return Rect(0,0,0,0); } } class EmptyDrawable : Drawable { - override void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0) { + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { } @property override int width() { return 0; } @property override int height() { return 0; } @@ -705,7 +705,7 @@ class SolidFillDrawable : Drawable { this(uint color) { _color = color; } - override void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0) { + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { if ((_color >> 24) != 0xFF) // not fully transparent buf.fillRect(rc, _color); } @@ -755,7 +755,7 @@ class ImageDrawable : Drawable { n3 = n4 = n3 + middledist; } } - override void drawTo(DrawBuf buf, Rect rc, int tilex0 = 0, int tiley0 = 0) { + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { if (_image.isNull) return; if (_image.hasNinePatch) { @@ -833,4 +833,138 @@ class ImageDrawable : Drawable { } } +import std.xml; +import std.algorithm; +import dlangui.widgets.styles; + +void extractStateFlag(ref string[string] attr, string attrName, State state, ref uint stateMask, ref uint stateValue) { + if (attrName in attr) { + string value = attr[attrName]; + if (value.equal("true")) + stateValue |= state; + stateMask |= state; + } +} + +/// converts XML attribute name to State (see http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) +void extractStateFlags(ref string[string] attr, ref uint stateMask, ref uint stateValue) { + extractStateFlag(attr, "state_pressed", State.Pressed, stateMask, stateValue); + extractStateFlag(attr, "state_focused", State.Focused, stateMask, stateValue); + extractStateFlag(attr, "state_hovered", State.Hovered, stateMask, stateValue); + extractStateFlag(attr, "state_selected", State.Selected, stateMask, stateValue); + extractStateFlag(attr, "state_checkable", State.Checkable, stateMask, stateValue); + extractStateFlag(attr, "state_checked", State.Checked, stateMask, stateValue); + extractStateFlag(attr, "state_enabled", State.Enabled, stateMask, stateValue); + extractStateFlag(attr, "state_activated", State.Activated, stateMask, stateValue); + extractStateFlag(attr, "state_window_focused", State.WindowFocused, stateMask, stateValue); +} + +/* +sample: +(prefix android: is optional) + + + + + +*/ + +/// Drawable which is drawn depending on state (see http://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) +class StateDrawable : Drawable { + + static struct StateItem { + uint stateMask; + uint stateValue; + DrawableRef drawable; + @property bool matchState(uint state) { + return (stateMask & state) == stateValue; + } + } + protected StateItem[] _stateList; + + void addState(uint stateMask, uint stateValue, string resourceId) { + StateItem item; + item.stateMask = stateMask; + item.stateValue = stateValue; + item.drawable = dlangui.graphics.images.drawableCache.get(resourceId); + _stateList ~= item; + } + + void addState(uint stateMask, uint stateValue, DrawableRef drawable) { + StateItem item; + item.stateMask = stateMask; + item.stateValue = stateValue; + item.drawable = drawable; + _stateList ~= item; + } + + bool load(Element element) { + foreach(item; element.elements) { + if (item.tag.name.equal("item")) { + if ("drawable" in item.tag.attr) { + string drawableId = item.tag.attr["drawable"]; + uint stateMask, stateValue; + extractStateFlags(item.tag.attr, stateMask, stateValue); + if (drawableId !is null) { + addState(stateMask, stateValue, drawableId); + } + } + } + } + return _stateList.length > 0; + } + + /// load from XML file + bool load(string filename) { + import std.file; + import std.string; + + try { + string s = cast(string)std.file.read(filename); + + // Check for well-formedness + check(s); + + // Make a DOM tree + auto doc = new Document(s); + + return load(doc); + } catch (Exception e) { + Log.e("Cannot read drawable resource from file ", filename); + return false; + } + } + + override void drawTo(DrawBuf buf, Rect rc, uint state = 0, int tilex0 = 0, int tiley0 = 0) { + foreach(ref item; _stateList) + if (item.matchState(state)) { + item.drawable.drawTo(buf, rc, state, tilex0, tiley0); + return; + } + } + + @property override int width() { + return (_stateList.length > 0) ? _stateList[0].drawable.width : 0; + } + @property override int height() { + return (_stateList.length > 0) ? _stateList[0].drawable.height : 0; + } + @property override Rect padding() { + return (_stateList.length > 0) ? _stateList[0].drawable.padding : Rect(0,0,0,0); + } +} + alias DrawableRef = Ref!Drawable; diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 59a30b33..ef5c85d6 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -341,15 +341,15 @@ class ScrollBar : AbstractSlider, OnClickHandler { return true; } if (event.action == MouseAction.Move && trackHover) { - if (!(state & State.Hover)) { + if (!(state & State.Hovered)) { Log.d("Hover ", id); - setState(State.Hover); + setState(State.Hovered); } return true; } if ((event.action == MouseAction.Leave || event.action == MouseAction.Cancel) && trackHover) { Log.d("Leave ", id); - resetState(State.Hover); + resetState(State.Hovered); return true; } if (event.action == MouseAction.Cancel) { diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 8f86c9ad..6d8bf1ec 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -20,14 +20,20 @@ immutable int FILL_PARENT = int.max - 1; immutable int WRAP_CONTENT = int.max - 2; immutable int WEIGHT_UNSPECIFIED = -1; +/// widget state flags - bits enum State : uint { + /// state not specified / normal Normal = 0, Pressed = 1, Focused = 2, - Disabled = 4, - Hover = 8, // mouse pointer is over control, buttons not pressed + Enabled = 4, + Hovered = 8, // mouse pointer is over control, buttons not pressed Selected = 16, - Parent = 128, // use parent's state + Checkable = 32, + Checked = 64, + Activated = 128, + WindowFocused = 256, + Parent = 0x10000, // use parent's state } enum Align : ubyte { @@ -621,11 +627,11 @@ Theme createDefaultTheme() { res.fontSize(14); Style button = res.createSubstyle("BUTTON").backgroundImageId("btn_default_small_normal").alignment(Align.Center); Style text = res.createSubstyle("TEXT").margins(Rect(2,2,2,2)).padding(Rect(1,1,1,1)); - button.createState(State.Disabled | State.Focused, State.Disabled | State.Focused).backgroundImageId("btn_default_small_normal_disable_focused"); - button.createState(State.Disabled, State.Disabled).backgroundImageId("btn_default_small_normal_disable"); + button.createState(State.Enabled | State.Focused, State.Focused).backgroundImageId("btn_default_small_normal_disable_focused"); + button.createState(State.Enabled, 0).backgroundImageId("btn_default_small_normal_disable"); button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed"); button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected"); - button.createState(State.Hover, State.Hover).backgroundImageId("btn_default_small_normal_hover"); + button.createState(State.Hovered, State.Hovered).backgroundImageId("btn_default_small_normal_hover"); res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_UP, "scrollbar_btn_up"); res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_DOWN, "scrollbar_btn_down"); res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_LEFT, "scrollbar_btn_left"); @@ -639,7 +645,7 @@ Theme createDefaultTheme() { Style scrollbarSlider = res.createSubstyle("SLIDER"); Style scrollbarPage = res.createSubstyle("PAGE_SCROLL").backgroundColor(0xFFFFFFFF); scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080); - scrollbarPage.createState(State.Hover, State.Hover).backgroundColor(0xF0404080); + scrollbarPage.createState(State.Hovered, State.Hovered).backgroundColor(0xF0404080); Style tabUp = res.createSubstyle("TAB_UP"); tabUp.backgroundImageId("tab_up_background"); @@ -650,13 +656,13 @@ Theme createDefaultTheme() { tabUpButtonText.createState(State.Selected, State.Selected).textColor(0x000000); tabUpButtonText.createState(State.Selected|State.Focused, State.Selected|State.Focused).textColor(0x000000); tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000); - tabUpButtonText.createState(State.Hover, State.Hover).textColor(0xFFE0E0); + tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0); Style tabUpButton = res.createSubstyle("TAB_UP_BUTTON"); tabUpButton.backgroundImageId("tab_btn_up_normal"); tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected"); tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected"); tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused"); - tabUpButton.createState(State.Hover, State.Hover).backgroundImageId("tab_btn_up_hover"); + tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover"); Style tabHost = res.createSubstyle("TAB_HOST"); tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); tabHost.backgroundColor(0xF0F0F0); diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index c2cb4249..ad4f29eb 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -317,7 +317,7 @@ class Widget { } if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { resetState(State.Pressed); - resetState(State.Hover); + resetState(State.Hovered); return true; } if (event.action == MouseAction.FocusIn) { @@ -325,15 +325,15 @@ class Widget { return true; } if (event.action == MouseAction.Move && trackHover) { - if (!(state & State.Hover)) { + if (!(state & State.Hovered)) { Log.d("Hover ", id); - setState(State.Hover); + setState(State.Hovered); } return true; } if (event.action == MouseAction.Leave && trackHover) { Log.d("Leave ", id); - resetState(State.Hover); + resetState(State.Hovered); return true; } }