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 00000000..405bb9f7 Binary files /dev/null and b/examples/example1/res/tab_btn_normal.9.png differ diff --git a/examples/example1/res/tab_btn_up_focused.9.png b/examples/example1/res/tab_btn_up_focused.9.png new file mode 100644 index 00000000..790a08e3 Binary files /dev/null and b/examples/example1/res/tab_btn_up_focused.9.png differ diff --git a/examples/example1/res/tab_btn_up_focused_selected.9.png b/examples/example1/res/tab_btn_up_focused_selected.9.png new file mode 100644 index 00000000..f69a9310 Binary files /dev/null and b/examples/example1/res/tab_btn_up_focused_selected.9.png differ diff --git a/examples/example1/res/tab_btn_up_hover.9.png b/examples/example1/res/tab_btn_up_hover.9.png new file mode 100644 index 00000000..fa378fe1 Binary files /dev/null and b/examples/example1/res/tab_btn_up_hover.9.png differ 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 00000000..8400847a Binary files /dev/null and b/examples/example1/res/tab_btn_up_normal.9.png differ diff --git a/examples/example1/res/tab_btn_up_selected.9.png b/examples/example1/res/tab_btn_up_selected.9.png new file mode 100644 index 00000000..dad1e90d Binary files /dev/null and b/examples/example1/res/tab_btn_up_selected.9.png differ 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 00000000..b3d0c95a Binary files /dev/null and b/examples/example1/res/tab_up_background.9.png differ 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 00000000..e4fa9118 Binary files /dev/null and b/examples/example1/res/tab_up_background_focused.9.png differ diff --git a/examples/example1/res/tab_up_background_selected.9.png b/examples/example1/res/tab_up_background_selected.9.png new file mode 100644 index 00000000..993bef44 Binary files /dev/null and b/examples/example1/res/tab_up_background_selected.9.png differ 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); } }