From 17c0d2557ef954db6ac8d5fd3a6a3a835947db57 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 1 Apr 2014 17:12:58 +0400 Subject: [PATCH] tabs --- examples/example1/main.d | 14 +- examples/example1/res/tab_btn_normal.9.png | Bin 0 -> 1145 bytes .../example1/res/tab_btn_up_focused.9.png | Bin 0 -> 2120 bytes .../res/tab_btn_up_focused_selected.9.png | Bin 0 -> 2120 bytes examples/example1/res/tab_btn_up_hover.9.png | Bin 0 -> 1099 bytes examples/example1/res/tab_btn_up_normal.9.png | Bin 0 -> 972 bytes .../example1/res/tab_btn_up_selected.9.png | Bin 0 -> 1190 bytes examples/example1/res/tab_up_background.9.png | Bin 0 -> 256 bytes .../res/tab_up_background_focused.9.png | Bin 0 -> 258 bytes .../res/tab_up_background_selected.9.png | Bin 0 -> 258 bytes src/dlangui/widgets/layouts.d | 81 ++++ src/dlangui/widgets/styles.d | 21 + src/dlangui/widgets/tabs.d | 402 ++++++++++++++++++ src/dlangui/widgets/widget.d | 41 +- 14 files changed, 544 insertions(+), 15 deletions(-) create mode 100644 examples/example1/res/tab_btn_normal.9.png create mode 100644 examples/example1/res/tab_btn_up_focused.9.png create mode 100644 examples/example1/res/tab_btn_up_focused_selected.9.png create mode 100644 examples/example1/res/tab_btn_up_hover.9.png create mode 100644 examples/example1/res/tab_btn_up_normal.9.png create mode 100644 examples/example1/res/tab_btn_up_selected.9.png create mode 100644 examples/example1/res/tab_up_background.9.png create mode 100644 examples/example1/res/tab_up_background_focused.9.png create mode 100644 examples/example1/res/tab_up_background_selected.9.png create mode 100644 src/dlangui/widgets/tabs.d diff --git a/examples/example1/main.d b/examples/example1/main.d index a0924eb3..329a344f 100644 --- a/examples/example1/main.d +++ b/examples/example1/main.d @@ -50,14 +50,12 @@ extern (C) int UIAppMain(string[] args) { LinearLayout layout = new LinearLayout(); TabWidget tabs = new TabWidget("TABS"); - tabs.addTab("id1", "Tab 1"d); - tabs.addTab("id2", "Tab 2 label"d); - tabs.addTab("id3", "Tab 3 label"d); - tabs.addTab("id4", "Tab 4 label"d); - tabs.addTab("id5", "Tab 5 label"d); - tabs.addTab("id6", "Tab 6 label"d); - tabs.addTab("id7", "Tab 7 label"d); - tabs.addTab("id8", "Tab 8 label"d); + tabs.addTab((new TextWidget()).id("tab1").textColor(0x00802000).text("Tab 1 contents"), "Tab 1"d); + tabs.addTab((new TextWidget()).id("tab2").textColor(0x00802000).text("Tab 2 contents"), "Tab 2"d); + tabs.addTab((new TextWidget()).id("tab3").textColor(0x00802000).text("Tab 3 contents"), "Tab 3"d); + tabs.addTab((new TextWidget()).id("tab4").textColor(0x00802000).text("Tab 4 contents"), "Tab 4"d); + tabs.addTab((new TextWidget()).id("tab5").textColor(0x00802000).text("Tab 5 contents"), "Tab 5"d); + tabs.selectTab("tab1"); layout.addChild(tabs); layout.addChild((new TextWidget()).textColor(0x00802000).text("Text widget 0")); diff --git a/examples/example1/res/tab_btn_normal.9.png b/examples/example1/res/tab_btn_normal.9.png new file mode 100644 index 0000000000000000000000000000000000000000..405bb9f7e5e33e639bd97c736e68946b570bd568 GIT binary patch literal 1145 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 R6{7$E002ovPDHLkV1lyY3E}_% literal 0 HcmV?d00001 diff --git a/examples/example1/res/tab_btn_up_normal.9.png b/examples/example1/res/tab_btn_up_normal.9.png new file mode 100644 index 0000000000000000000000000000000000000000..8400847aadc4857b559b7034f389c5d119a078a1 GIT binary patch literal 972 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 Ef|JZC5C8xG literal 0 HcmV?d00001 diff --git a/examples/example1/res/tab_up_background.9.png b/examples/example1/res/tab_up_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..b3d0c95a515d6e31250d708b4a0ebb7960a266d8 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^szB_?!3HFEt$42lq!^2X+?^QKos)S9vL>4nJ zaCd?*qxs3xYk`97C7!;n?Dtq0*%`DM^ncC=3Qh8KaSX}0_x9Fa-a`&NYyrQg6y9K1 z7_FSdd?YSXMSBCEJ*(`_rY?z>wR6{3{i<1Nysh%s`^mE(7FfM!lzg^L@8+U}Tj`~u zKDtVBM+3fZdfnVa2uLjm3jW_UOG!!Z^G2Q*yY%uy-tP?xeCb~sd-Xx?ckA=woA2-_ UJ`^vL2fB{I)78&qol`;+0G1?SSpWb4 literal 0 HcmV?d00001 diff --git a/examples/example1/res/tab_up_background_focused.9.png b/examples/example1/res/tab_up_background_focused.9.png new file mode 100644 index 0000000000000000000000000000000000000000..e4fa9118cea87b2f7983a1581db52a44b04a8cc3 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^szB_?!3HFEt$42lq!^2X+?^QKos)S9vL>4nJ zaCd?*qxs3xYk`97C7!;n?Dtq0+2k~>7oCj;3Qh5JaSX}0_x6^dV5@&5R$EX)xH zj^;DeIy86ZGct2DUT~I@ib#9+Ht6Y@w9^sSH_bag-T7`wzC)Mp|BIfC-p?<(8mM|! z8(b0l_S2z}iIq!4BVYkuhSq{JOZIhbQsMn9^6Yo?|Lk1PQvL>4nJ zaCd?*qxs3xYk`97C7!;n?Dtq0*%>TaChl+q3Qh5JaSX}0_x9FC!9xZ-4uL1TH!x^# zVC6NCzp#N-JB5+W)5mf8l7)(o-u*py*E({k(X_8WYIjz=;eW2Hu$uRL^frx%+5PL9 zR=OSu;W3@|*G_{FSRooR>&pH`MoN!?NkmUZn}`Mmts X8Uj7B!JcYB_c3_7`njxgN@xNA|ASWY literal 0 HcmV?d00001 diff --git a/src/dlangui/widgets/layouts.d b/src/dlangui/widgets/layouts.d index 9c716918..b877aabc 100644 --- a/src/dlangui/widgets/layouts.d +++ b/src/dlangui/widgets/layouts.d @@ -254,6 +254,7 @@ class LinearLayout : WidgetGroup { /// 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) { + _needLayout = false; return; } _pos = rc; @@ -295,3 +296,83 @@ class HorizontalLayout : LinearLayout { } } +/// place all children into same place (usually, only one child should be visible at a time) +class FrameLayout : WidgetGroup { + this(string ID) { + super(ID); + } + /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). + override void measure(int parentWidth, int parentHeight) { + Rect m = margins; + Rect p = padding; + // calc size constraints for children + int pwidth = parentWidth; + int pheight = parentHeight; + if (parentWidth != SIZE_UNSPECIFIED) + pwidth -= m.left + m.right + p.left + p.right; + if (parentHeight != SIZE_UNSPECIFIED) + pheight -= m.top + m.bottom + p.top + p.bottom; + // measure children + Point sz; + for (int i = 0; i < _children.count; i++) { + Widget item = _children.get(i); + if (item.visibility != Visibility.Gone) { + item.measure(pwidth, pheight); + if (sz.x < item.measuredWidth) + sz.x = item.measuredWidth; + if (sz.y < item.measuredHeight) + sz.y = item.measuredHeight; + } + } + measuredContent(parentWidth, parentHeight, sz.x, sz.y); + } + + /// 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) { + _needLayout = false; + return; + } + _pos = rc; + applyMargins(rc); + applyPadding(rc); + for (int i = 0; i < _children.count; i++) { + Widget item = _children.get(i); + if (item.visibility != Visibility.Gone) { + item.layout(rc); + } + } + } + + /// Draw widget at its position to buffer + override void onDraw(DrawBuf buf) { + if (visibility != Visibility.Visible) + return; + super.onDraw(buf); + Rect rc = _pos; + applyMargins(rc); + applyPadding(rc); + ClipRectSaver(buf, rc); + for (int i = 0; i < _children.count; i++) { + Widget item = _children.get(i); + if (item.visibility != Visibility.Visible) + continue; + item.onDraw(buf); + } + } + + /// make one of children (with specified ID) visible, for the rest, set visibility to otherChildrenVisibility + bool showChild(string ID, Visibility otherChildrenVisibility = Visibility.Invisible) { + bool found = false; + for (int i = 0; i < _children.count; i++) { + Widget item = _children.get(i); + if (item.compareId(ID)) { + item.visibility = Visibility.Visible; + found = true; + } else { + item.visibility = otherChildrenVisibility; + } + } + return found; + } +} diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 663ad845..38d06cde 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -26,6 +26,8 @@ enum State : uint { Focused = 2, Disabled = 4, Hover = 8, // mouse pointer is over control, buttons not pressed + Selected = 16, + Parent = 128, // use parent's state } enum Align : ubyte { @@ -616,6 +618,7 @@ immutable ATTR_SCROLLBAR_INDICATOR_HORIZONTAL = "scrollbar_indicator_horizontal" Theme createDefaultTheme() { Log.d("Creating default theme"); Theme res = new Theme("default"); + 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"); @@ -638,6 +641,24 @@ Theme createDefaultTheme() { scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080); scrollbarPage.createState(State.Hover, State.Hover).backgroundColor(0xF0404080); + Style tabUp = res.createSubstyle("TAB_UP"); + tabUp.backgroundImageId("tab_up_background"); + tabUp.layoutWidth(FILL_PARENT); + tabUp.createState(State.Selected, State.Selected).backgroundImageId("tab_up_backgrond_selected"); + Style tabUpButtonText = res.createSubstyle("TAB_UP_BUTTON_TEXT"); + tabUpButtonText.textColor(0xFFFFFF).fontSize(12); + 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(0x404000); + 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"); + Style tabHost = res.createSubstyle("TAB_HOST"); + tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); //res.dumpStats(); return res; } diff --git a/src/dlangui/widgets/tabs.d b/src/dlangui/widgets/tabs.d new file mode 100644 index 00000000..f78bc47a --- /dev/null +++ b/src/dlangui/widgets/tabs.d @@ -0,0 +1,402 @@ +module dlangui.widgets.tabs; + +import dlangui.widgets.layouts; +import dlangui.widgets.controls; + +class TabItem { + private string _iconRes; + private string _id; + private UIString _label; + private long _lastAccessTs; + this(string id, string labelRes, string iconRes = null) { + _id = id; + _label = labelRes; + _iconRes = iconRes; + } + this(string id, dstring labelRes, string iconRes = null) { + _id = id; + _label = labelRes; + _iconRes = iconRes; + _lastAccessTs = std.datetime.Clock.currStdTime; + } + @property string iconId() const { return _iconRes; } + @property string id() const { return _id; } + @property ref UIString text() { return _label; } + @property TabItem iconId(string id) { _iconRes = id; return this; } + @property TabItem id(string id) { _id = id; return this; } + @property long lastAccessTs() { return _lastAccessTs; } + @property void lastAccessTs(long ts) { _lastAccessTs = ts; } + void updateAccessTs() { _lastAccessTs = std.datetime.Clock.currStdTime; } +} + +class TabItemWidget : HorizontalLayout { + private ImageWidget _icon; + private TextWidget _label; + private ImageButton _closeButton; + private TabItem _item; + private bool _enableCloseButton; + @property TabItem tabItem() { return _item; } + @property TabControl tabControl() { return cast(TabControl)parent; } + this(TabItem item, bool enableCloseButton = true) { + styleId = "TAB_UP_BUTTON"; + _enableCloseButton = enableCloseButton; + _icon = new ImageWidget(); + _label = new TextWidget(); + _label.styleId = "TAB_UP_BUTTON_TEXT"; + _label.state = State.Parent; + _closeButton = new ImageButton("CLOSE"); + _closeButton.drawableId = "close"; + _closeButton.onClickListener = &onClick; + if (_enableCloseButton) { + _closeButton.visibility = Visibility.Gone; + } else { + _closeButton.visibility = Visibility.Visible; + } + addChild(_icon); + addChild(_label); + addChild(_closeButton); + setItem(item); + trackHover = true; + } + protected bool onClick(Widget source) { + if (source.compareId("CLOSE")) { + Log.d("tab close button pressed"); + } + return true; + } + protected void setItem(TabItem item) { + _item = item; + if (item.iconId !is null) { + _icon.visibility = Visibility.Visible; + _icon.drawableId = item.iconId; + } else { + _icon.visibility = Visibility.Gone; + } + _label.text = item.text; + id = item.id; + } +} + +/// tab item list helper class +class TabItemList { + private TabItem[] _list; + private int _len; + + this() { + } + + /// get item by index + TabItem get(int index) { + if (index < 0 || index >= _len) + return null; + return _list[index]; + } + /// get item by id + TabItem get(string id) { + int idx = indexById(id); + if (idx < 0) + return null; + return _list[idx]; + } + @property int length() const { return _len; } + /// append new item + TabItemList add(TabItem item) { + return insert(item, -1); + } + /// insert new item to specified position + TabItemList insert(TabItem item, int index) { + if (index > _len || index < 0) + index = _len; + if (_list.length <= _len) + _list.length = _len + 4; + for (int i = _len; i > index; i--) + _list[i] = _list[i - 1]; + _list[index] = item; + _len++; + return this; + } + /// remove item by index + TabItem remove(int index) { + TabItem res = _list[index]; + for (int i = index; i < _len - 1; i++) + _list[i] = _list[i + 1]; + _len--; + return res; + } + /// find tab index by id + int indexById(string id) { + import std.algorithm; + for (int i = 0; i < _len; i++) { + if (_list[i].id.equal(id)) + return i; + } + return -1; + } +} + +class TabControl : WidgetGroup { + protected TabItemList _items; + protected ImageButton _moreButton; + protected bool _enableCloseButton; + protected TabItemWidget[] _sortedItems; + + this(string ID) { + super(ID); + _items = new TabItemList(); + _moreButton = new ImageButton("MORE", "tab_more"); + _moreButton.onClickListener = &onClick; + _enableCloseButton = true; + styleId = "TAB_UP"; + addChild(_moreButton); // first child is always MORE button, the rest corresponds to tab list + } + /// returns tab count + @property int tabCount() const { + return _items.length; + } + /// returns tab item by id (null if index out of range) + TabItem tab(int index) { + return _items.get(index); + } + /// returns tab item by id (null if not found) + TabItem tab(string id) { + return _items.get(id); + } + /// get tab index by tab id (-1 if not found) + int tabIndex(string id) { + return _items.indexById(id); + } + protected void updateTabs() { + // TODO: + } + static bool accessTimeComparator(TabItemWidget a, TabItemWidget b) { + return (a.tabItem.lastAccessTs > b.tabItem.lastAccessTs); + } + protected TabItemWidget[] sortedItems() { + _sortedItems.length = _items.length; + for (int i = 0; i < _items.length; i++) + _sortedItems[i] = cast(TabItemWidget)_children.get(i + 1); + std.algorithm.sort!(accessTimeComparator)(_sortedItems); + return _sortedItems; + } + /// remove tab + TabControl removeTab(string id) { + int index = _items.indexById(id); + if (index >= 0) { + _children.remove(index + 1); + _items.remove(index); + requestLayout(); + } + return this; + } + /// add new tab + TabControl addTab(TabItem item, int index = -1, bool enableCloseButton = false) { + _items.insert(item, index); + TabItemWidget widget = new TabItemWidget(item, enableCloseButton); + widget.parent = this; + widget.onClickListener = &onClick; + _children.insert(widget, index); + updateTabs(); + requestLayout(); + return this; + } + /// add new tab by id and label string + TabControl addTab(string id, dstring label, string iconId = null, bool enableCloseButton = false) { + TabItem item = new TabItem(id, label, iconId); + return addTab(item, -1, enableCloseButton); + } + /// add new tab by id and label string resource id + TabControl addTab(string id, string labelResourceId, string iconId = null, bool enableCloseButton = false) { + TabItem item = new TabItem(id, labelResourceId, iconId); + return addTab(item, -1, enableCloseButton); + } + protected bool onClick(Widget source) { + if (source.compareId("MORE")) { + Log.d("tab MORE button pressed"); + return true; + } + string id = source.id; + int index = tabIndex(id); + if (index >= 0) { + selectTab(index); + } + return true; + } + /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). + override void measure(int parentWidth, int parentHeight) { + Rect m = margins; + Rect p = padding; + // calc size constraints for children + int pwidth = parentWidth; + int pheight = parentHeight; + if (parentWidth != SIZE_UNSPECIFIED) + pwidth -= m.left + m.right + p.left + p.right; + if (parentHeight != SIZE_UNSPECIFIED) + pheight -= m.top + m.bottom + p.top + p.bottom; + // measure children + Point sz; + _moreButton.measure(pwidth, pheight); + sz.x = _moreButton.measuredWidth; + sz.y = _moreButton.measuredHeight; + pwidth -= sz.x; + for (int i = 1; i < _children.count; i++) { + Widget tab = _children.get(i); + tab.visibility = Visibility.Visible; + tab.measure(pwidth, pheight); + if (sz.y < tab.measuredHeight) + sz.y = tab.measuredHeight; + if (sz.x + tab.measuredWidth > pwidth) + break; + sz.x += tab.measuredWidth; + } + measuredContent(parentWidth, parentHeight, sz.x, sz.y); + } + /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). + override void layout(Rect rc) { + _needLayout = false; + if (visibility == Visibility.Gone) { + return; + } + _pos = rc; + applyMargins(rc); + applyPadding(rc); + // more button + Rect moreRc = rc; + moreRc.left = rc.right - _moreButton.measuredWidth; + _moreButton.layout(moreRc); + rc.right -= _moreButton.measuredWidth; + // tabs + int maxw = rc.width; + // measure and update visibility + TabItemWidget[] sorted = sortedItems(); + int w = 0; + for (int i = 0; i < sorted.length; i++) { + TabItemWidget widget = sorted[i]; + widget.visibility = Visibility.Visible; + widget.measure(rc.width, rc.height); + if (w + widget.measuredWidth < maxw) { + w += widget.measuredWidth; + } else { + widget.visibility = Visibility.Gone; + } + } + // layout visible items + for (int i = 1; i < _children.count; i++) { + TabItemWidget widget = cast(TabItemWidget)_children.get(i); + if (widget.visibility != Visibility.Visible) + continue; + w = widget.measuredWidth; + rc.right = rc.left + w; + widget.layout(rc); + rc.left += w; + } + } + /// Draw widget at its position to buffer + override void onDraw(DrawBuf buf) { + if (visibility != Visibility.Visible) + return; + super.onDraw(buf); + Rect rc = _pos; + applyMargins(rc); + applyPadding(rc); + ClipRectSaver(buf, rc); + for (int i = 0; i < _children.count; i++) { + Widget item = _children.get(i); + if (item.visibility != Visibility.Visible) + continue; + item.onDraw(buf); + } + } + + void selectTab(int index) { + for (int i = 1; i < _children.count; i++) { + if (index == i - 1) { + _children.get(i).state = State.Selected; + } else { + _children.get(i).state = State.Normal; + } + } + } +} + +/// container for widgets controlled by TabControl +class TabHost : FrameLayout { + this(string ID, TabControl tabControl) { + super(ID); + _tabControl = tabControl; + styleId = "TAB_HOST"; + } + protected TabControl _tabControl; + /// get currently set control widget + @property TabControl tabControl() { return _tabControl; } + /// set new control widget + @property TabHost tabControl(TabControl newWidget) { _tabControl = newWidget; return this; } + + /// remove tab + TabHost removeTab(string id) { + assert(_tabControl !is null, "No TabControl set for TabHost"); + Widget child = removeChild(id); + if (child !is null) { + destroy(child); + } + _tabControl.removeTab(id); + requestLayout(); + return this; + } + /// add new tab by id and label string + TabHost addTab(Widget widget, dstring label, string iconId = null, bool enableCloseButton = false) { + assert(_tabControl !is null, "No TabControl set for TabHost"); + assert(widget.id !is null, "ID for tab host page is mandatory"); + assert(_children.indexOf(id) == -1, "duplicate ID for tab host page"); + _tabControl.addTab(widget.id, label, iconId, enableCloseButton); + return this; + } + /// add new tab by id and label string resource id + TabHost addTab(Widget widget, string labelResourceId, string iconId = null, bool enableCloseButton = false) { + assert(_tabControl !is null, "No TabControl set for TabHost"); + assert(widget.id !is null, "ID for tab host page is mandatory"); + assert(_children.indexOf(id) == -1, "duplicate ID for tab host page"); + _tabControl.addTab(widget.id, labelResourceId, iconId, enableCloseButton); + TabItem item = new TabItem(id, labelResourceId); + return this; + } + /// select tab + void selectTab(string ID) { + int index = _tabControl.tabIndex(ID); + if (index != -1) { + _tabControl.selectTab(index); + } + } +} + +/// compound widget - contains from TabControl widget (tabs header) and TabHost (content pages) +class TabWidget : VerticalLayout { + protected TabControl _tabControl; + protected TabHost _tabHost; + this(string ID) { + super(ID); + _tabControl = new TabControl("TAB_CONTROL"); + _tabHost = new TabHost("TAB_HOST", _tabControl); + addChild(_tabControl); + addChild(_tabHost); + } + /// add new tab by id and label string resource id + TabWidget addTab(Widget widget, string labelResourceId, string iconId = null, bool enableCloseButton = false) { + _tabHost.addTab(widget, labelResourceId, iconId, enableCloseButton); + return this; + } + /// add new tab by id and label (raw value) + TabWidget addTab(Widget widget, dstring label, string iconId = null, bool enableCloseButton = false) { + _tabHost.addTab(widget, label, iconId, enableCloseButton); + return this; + } + /// remove tab by id + TabWidget removeTab(string id) { + _tabHost.removeTab(id); + requestLayout(); + return this; + } + /// select tab + void selectTab(string ID) { + _tabHost.selectTab(ID); + } +} diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index ea389697..e1b4e2f8 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -123,12 +123,14 @@ class Widget { /// returns widget id, null if not set @property string id() const { return _id; } /// set widget id - @property void id(string id) { _id = id; } + @property Widget id(string id) { _id = id; return this; } /// compare widget id with specified value, returs true if matches bool compareId(string id) const { return (_id !is null) && id.equal(_id); } /// widget state (set of flags from State enum) @property uint state() const { + if ((_state & State.Parent) != 0 && _parent !is null) + return _parent.state; return _state; } /// set new widget state (set of flags from State enum) @@ -187,7 +189,7 @@ class Widget { /// set background color for widget - override one from style @property Widget backgroundColor(uint color) { ownStyle.backgroundColor = color; return this; } /// get text color (ARGB 32 bit value) - @property uint textColor() const { return style.textColor; } + @property uint textColor() const { return stateStyle.textColor; } /// set text color (ARGB 32 bit value) @property Widget textColor(uint value) { ownStyle.textColor = value; return this; } /// returns font face @@ -462,9 +464,11 @@ class Widget { /// returns child by index Widget child(int index) { return null; } /// adds child, returns added item - Widget addChild(Widget item) { assert(false, "children not suported for this widget type"); } - /// removes child, returns added item - Widget removeChild(int index) { assert(false, "children not suported for this widget type"); } + Widget addChild(Widget item) { assert(false, "addChild: children not suported for this widget type"); } + /// removes child, returns removed item + Widget removeChild(int index) { assert(false, "removeChild: children not suported for this widget type"); } + /// removes child by ID, returns removed item + Widget removeChild(string id) { assert(false, "removeChild: children not suported for this widget type"); } /// returns index of widget in child list, -1 if passed widget is not a child of this widget int childIndex(Widget item) { return -1; } @@ -568,6 +572,13 @@ struct WidgetList { return i; return -1; } + /// find child index for item by id, return -1 if not found + int indexOf(string id) { + for (int i = 0; i < _count; i++) + if (_list[i].compareId(id)) + return i; + return -1; + } /// remove item from list, return removed item Widget remove(int index) { assert(index >= 0 && index < _count, "child index out of range"); @@ -605,8 +616,24 @@ class WidgetGroup : Widget { override Widget child(int index) { return _children.get(index); } /// adds child, returns added item override Widget addChild(Widget item) { return _children.add(item).parent(this); } - /// removes child, returns added item - override Widget removeChild(int index) { return _children.remove(index); } + /// removes child, returns removed item + override Widget removeChild(int index) { + Widget res = _children.remove(index); + if (res !is null) + res.parent = null; + return res; + } + /// removes child by ID, returns removed item + override Widget removeChild(string ID) { + Widget res = null; + int index = _children.indexOf(ID); + if (index < 0) + return null; + res = _children.remove(index); + if (res !is null) + res.parent = null; + return res; + } /// returns index of widget in child list, -1 if passed widget is not a child of this widget override int childIndex(Widget item) { return _children.indexOf(item); } }