diff --git a/examples/example1/src/main.d b/examples/example1/src/main.d index 68701bf5..7a2f8852 100644 --- a/examples/example1/src/main.d +++ b/examples/example1/src/main.d @@ -623,6 +623,15 @@ extern (C) int UIAppMain(string[] args) { // tree view example TreeWidget tree = new TreeWidget("TREE1"); tree.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT); + TreeItem tree1 = tree.items.newChild("group1", "Group 1"d); + tree1.newChild("g1_1", "Group 1 item 1"d); + tree1.newChild("g1_2", "Group 1 item 2"d); + tree1.newChild("g1_3", "Group 1 item 3"d); + TreeItem tree2 = tree.items.newChild("group2", "Group 2"d); + tree2.newChild("g2_1", "Group 2 item 1"d); + tree2.newChild("g2_2", "Group 2 item 2"d); + tree2.newChild("g2_3", "Group 2 item 3"d); + tree2.newChild("g2_4", "Group 2 item 4"d); tabs.addTab(tree, "Tree"d); diff --git a/res/mdpi/arrow_right_down_black.png b/res/mdpi/arrow_right_down_black.png new file mode 100644 index 00000000..e9e92bd8 Binary files /dev/null and b/res/mdpi/arrow_right_down_black.png differ diff --git a/res/mdpi/arrow_right_hollow.png b/res/mdpi/arrow_right_hollow.png new file mode 100644 index 00000000..8c0aab51 Binary files /dev/null and b/res/mdpi/arrow_right_hollow.png differ diff --git a/src/dlangui/widgets/tree.d b/src/dlangui/widgets/tree.d index 31107e34..527504b2 100644 --- a/src/dlangui/widgets/tree.d +++ b/src/dlangui/widgets/tree.d @@ -3,6 +3,7 @@ module dlangui.widgets.tree; import dlangui.widgets.widget; import dlangui.widgets.controls; import dlangui.widgets.scroll; +import dlangui.widgets.layouts; import std.conv; import std.algorithm; @@ -16,6 +17,36 @@ class TreeItem { protected ObjectList!TreeItem _children; protected bool _expanded; + this(string id) { + _id = id; + _expanded = true; + } + this(string id, dstring label, string iconRes = null) { + _id = id; + _expanded = true; + _iconRes = iconRes; + _text = label; + } + this(string id, UIString label, string iconRes = null) { + _id = id; + _expanded = true; + _iconRes = iconRes; + _text = label; + } + this(string id, string labelRes, string iconRes = null) { + _id = id; + _expanded = true; + _iconRes = iconRes; + _text = labelRes; + } + /// create and add new child item + TreeItem newChild(string id, dstring label, string iconRes = null) { + TreeItem res = new TreeItem(id, label, iconRes); + addChild(res); + return res; + } + + @property TreeItem parent() { return _parent; } @property protected TreeItem parent(TreeItem p) { _parent = p; return this; } @property string id() { return _id; } @@ -31,11 +62,16 @@ class TreeItem { } @property bool expanded() { return _expanded; } @property protected TreeItem expanded(bool expanded) { _expanded = expanded; return this; } + /** Returns true if this item and all parents are expanded. */ bool isFullyExpanded() { if (!_expanded) return false; + return isVisible(); + } + /** Returns true if all parents are expanded. */ + bool isVisible() { if (_parent) - return _parent.isFullyExpanded(); + return _parent.isVisible(); return false; } void expand() { @@ -72,6 +108,9 @@ class TreeItem { return _parent.topParent; } + /// returns true if item has at least one child + @property bool hasChildren() { return childCount > 0; } + /// returns number of children of this widget @property int childCount() { return _children.count; } /// returns child by index @@ -100,7 +139,11 @@ class TreeItem { } /// returns index of widget in child list, -1 if passed widget is not a child of this widget int childIndex(TreeItem item) { return _children.indexOf(item); } - + /// notify listeners + protected void onUpdate(TreeItem item) { + if (_parent) + _parent.onUpdate(item); + } } interface OnTreeContentChangeListener { @@ -109,36 +152,114 @@ interface OnTreeContentChangeListener { class TreeItems : TreeItem { // signal handler OnTreeContentChangeListener - Listener!OnTreeContentChangeListener listener; + Signal!OnTreeContentChangeListener listener; + + this() { + super("tree"); + } + + /// notify listeners + override protected void onUpdate(TreeItem item) { + if (listener.assigned) + listener(this); + } } -class TreeWidgetBase : ScrollWidgetBase { +class TreeItemWidget : HorizontalLayout { + TreeItem _item; + TextWidget _tab; + ImageWidget _expander; + ImageWidget _icon; + TextWidget _label; + this(TreeItem item) { + super(item.id); + _item = item; + _tab = new TextWidget("tab"); + dchar[] tabText; + dchar[] singleTab = [' ', ' ', ' ', ' ']; + for (int i = 1; i < _item.level; i++) + tabText ~= singleTab; + _tab.text = cast(dstring)tabText; + _expander = new ImageWidget("expander", _item.hasChildren && _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"); + if (!_item.hasChildren) + _expander.visibility = Visibility.Gone; + if (_item.iconRes.length > 0) + _icon = new ImageWidget("icon", _item.iconRes); + _label = new TextWidget("label", _item.text); + addChild(_tab); + addChild(_expander); + if (_icon) + addChild(_icon); + addChild(_label); + } +} + +class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener { + + protected TreeItems _tree; + + @property ref TreeItems items() { return _tree; } + + protected bool _needUpdateWidgets; this(string ID = null, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) { super(ID, hscrollbarMode, vscrollbarMode); + contentWidget = new VerticalLayout("TREE_CONTENT"); + _tree = new TreeItems(); + _tree.listener.connect(this); + _needUpdateWidgets = true; } - /// process horizontal scrollbar event - override bool onHScroll(ScrollEvent event) { - return true; + ~this() { + if (_tree) { + destroy(_tree); + _tree = null; + } } - /// process vertical scrollbar event - override bool onVScroll(ScrollEvent event) { - return true; + /** Override to use custom tree item widgets. */ + protected Widget createItemWidget(TreeItem item) { + return new TreeItemWidget(item); } - /// update horizontal scrollbar widget position - override protected void updateHScrollBar() { - // override it + protected void addWidgets(TreeItem item) { + if (item.level > 0) + _contentWidget.addChild(createItemWidget(item)); + for (int i = 0; i < item.childCount; i++) + addWidgets(item.child(i)); } - /// update verticat scrollbar widget position - override protected void updateVScrollBar() { - // override it + protected void updateWidgets() { + _contentWidget.removeAllChildren(); + addWidgets(_tree); + _needUpdateWidgets = false; } + /// 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) { + return; + } + if (_needUpdateWidgets) + updateWidgets(); + super.layout(rc); + } + /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). + override void measure(int parentWidth, int parentHeight) { + if (visibility == Visibility.Gone) { + return; + } + if (_needUpdateWidgets) + updateWidgets(); + super.measure(parentWidth, parentHeight); + } + + /// listener + override void onTreeContentChange(TreeItems source) { + _needUpdateWidgets = true; + requestLayout(); + } } class TreeWidget : TreeWidgetBase { diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 49bb6857..b61cf896 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1207,7 +1207,10 @@ class Widget { /// sets window (to be used for top level widget from Window implementation). TODO: hide it from API? @property void window(Window window) { _window = window; } - + void removeAllChildren() { + // override + } + } /// object list holder, owning its objects - on destroy of holder, all own objects will be destroyed @@ -1316,4 +1319,9 @@ class WidgetGroup : Widget { } /// 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); } + + override void removeAllChildren() { + _children.clear(); + } + }