From 9fd29770a93e24c3cdc200d570c2c04a2ddb8a94 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin <coolreader.org@gmail.com> Date: Thu, 4 Dec 2014 11:53:36 +0300 Subject: [PATCH] tree widget - almost usable --- examples/example1/src/main.d | 23 ++- res/mdpi/arrow_right_down_black.png | Bin 160 -> 154 bytes res/mdpi/arrow_right_hollow.png | Bin 2873 -> 2877 bytes res/theme_default.xml | 10 +- src/dlangui/widgets/controls.d | 6 +- src/dlangui/widgets/tree.d | 253 ++++++++++++++++++++++++---- src/dlangui/widgets/widget.d | 2 - 7 files changed, 252 insertions(+), 42 deletions(-) diff --git a/examples/example1/src/main.d b/examples/example1/src/main.d index 0f600c50..74394a72 100644 --- a/examples/example1/src/main.d +++ b/examples/example1/src/main.d @@ -623,15 +623,28 @@ 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); + TreeItem tree1 = tree.items.newChild("group1", "Group 1"d, "document-open"); 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); + TreeItem tree2 = tree.items.newChild("group2", "Group 2"d, "document-save"); + tree2.newChild("g2_1", "Group 2 item 1"d, "edit-copy"); + tree2.newChild("g2_2", "Group 2 item 2"d, "edit-cut"); + tree2.newChild("g2_3", "Group 2 item 3"d, "edit-paste"); tree2.newChild("g2_4", "Group 2 item 4"d); + TreeItem tree3 = tree.items.newChild("group3", "Group 3"d); + tree3.newChild("g3_1", "Group 3 item 1"d); + tree3.newChild("g3_2", "Group 3 item 2"d); + TreeItem tree32 = tree3.newChild("g3_3", "Group 3 item 3"d); + tree3.newChild("g3_4", "Group 3 item 4"d); + tree32.newChild("group3_2_1", "Group 3 item 2 subitem 1"d); + tree32.newChild("group3_2_2", "Group 3 item 2 subitem 2"d); + tree32.newChild("group3_2_3", "Group 3 item 2 subitem 3"d); + tree32.newChild("group3_2_4", "Group 3 item 2 subitem 4"d); + tree32.newChild("group3_2_5", "Group 3 item 2 subitem 5"d); + tree3.newChild("g3_5", "Group 3 item 5"d); + tree3.newChild("g3_6", "Group 3 item 6"d); + tree.items.selectItem(tree.items.child(0)); tabs.addTab(tree, "Tree"d); diff --git a/res/mdpi/arrow_right_down_black.png b/res/mdpi/arrow_right_down_black.png index e9e92bd81fdf4bd128e748c77ff7d5460b6542f8..e4adc299332bea75dd819e06c02383eb25ae813d 100644 GIT binary patch delta 119 zcmZ3$IE!(DR>6M;AY8C_`D6wL2F?PH$YKTtZeb8+WSBKa0w~B{;_2(kevgNRL(eKV zOQ#<wr0(hB7@~3h?WKi`3=A9%26o>|{v2?cx{s|UJa_?X6d3(Fe2^*WwL~g2Yo9hy O3xlVtpUXO@geCxo(j|WY delta 125 zcmbQmxPWnjR@p=bAk6!7<P-w~180FpWHAE+w=f7ZGR&GI0Tg5}@$_|NzsJMK&MWBr zS9BpzNY~TFF+}71)k_<B84NfM7}&jk`A$$$;O5@cXF=;7ShWHgxk45&iLP*9y*2+2 ZbIT+f`;(?Gf`A$sJYD@<);T3K0RSsRC;I>Z diff --git a/res/mdpi/arrow_right_hollow.png b/res/mdpi/arrow_right_hollow.png index 8c0aab510e6a5424df2c88c1edf7f80d9056110c..1a30a31200d6fcb9a3554b15a272d99105d84bc7 100644 GIT binary patch delta 126 zcmV-^0D=Fx7QGg*tO^PL0RI60puMM)leG#UXaoo|AK3zw)BpegNJ&INR4C8QkueSc zAP57i|6j_7&xyo1n4k`JV!0~>>8se5B#vMp;jN%Q-yoX-Y*P}<a9OazCBd`i<D1Li g^`9dm$EGhc0Fg;!04m)+VgLXD07*qoM6N<$f>9PVH~;_u delta 122 zcmV-=0EPd(7P%I%tO^N{0FeNZZv4c`leG#UWB~^XH88(tw*UYDL`g(JR4C7_k}(PZ zFbKr_dY|Bn)1?I|9rQHdXhc1;VK}v<W_Onb0J4CKA|O%~@KO=*BP*DjEblB|TmEw^ cVV!KJ**#sMw#JtuIsgCw07*qoM6N<$f{*?%S^xk5 diff --git a/res/theme_default.xml b/res/theme_default.xml index 21cd1b64..1f735434 100644 --- a/res/theme_default.xml +++ b/res/theme_default.xml @@ -172,15 +172,19 @@ <state state_hovered="true" backgroundColor="#C0FFFF00"/> </style> <style id="TREE_ITEM_EXPAND_ICON" - align="Center" + margins="2,0,2,0" + align="Left|VCenter" textFlags="Parent" /> <style id="TREE_ITEM_ICON" - align="Center" + margins="2,0,2,0" + align="Left|VCenter" textFlags="Parent" /> <style id="TREE_ITEM_LABEL" - align="Center" + layoutWidth="FILL_PARENT" + layoutHeight="WRAP_CONTENT" + align="Left|VCenter" textFlags="Parent" /> </theme> diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 2adb7e91..d7b381ed 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -406,7 +406,11 @@ class AbstractSlider : WidgetGroup { return this; } - protected bool sendScrollEvent(ScrollAction action, int position) { + bool sendScrollEvent(ScrollAction action) { + return sendScrollEvent(action, _position); + } + + bool sendScrollEvent(ScrollAction action, int position) { if (!onScrollEventListener.assigned) return false; ScrollEvent event = new ScrollEvent(action, _minValue, _maxValue, _pageSize, position); diff --git a/src/dlangui/widgets/tree.d b/src/dlangui/widgets/tree.d index 26d28b0c..22dd5689 100644 --- a/src/dlangui/widgets/tree.d +++ b/src/dlangui/widgets/tree.d @@ -46,6 +46,13 @@ class TreeItem { return res; } + /// returns topmost item + @property TreeItems root() { + TreeItem p = this; + while (p._parent) + p = p._parent; + return cast(TreeItems)p; + } @property TreeItem parent() { return _parent; } @property protected TreeItem parent(TreeItem p) { _parent = p; return this; } @@ -86,9 +93,7 @@ class TreeItem { } @property TreeItem selectedItem() { - if (_parent) - return _parent.selectedItem(); - return null; + return root.selectedItem(); } bool isSelected() { @@ -156,19 +161,16 @@ class TreeItem { int childIndex(TreeItem item) { return _children.indexOf(item); } /// notify listeners protected void onUpdate(TreeItem item) { - if (_parent) - _parent.onUpdate(item); + root.onUpdate(item); } - protected void toggleExpand(TreeItem item) { - - if (_parent) - _parent.toggleExpand(item); + root.toggleExpand(item); } - protected void selectItem(TreeItem item) { - if (_parent) - _parent.selectItem(item); + root.selectItem(item); + } + protected void activateItem(TreeItem item) { + root.activateItem(item); } } @@ -180,10 +182,16 @@ interface OnTreeStateChangeListener { void onTreeStateChange(TreeItems source); } +interface OnTreeSelectionChangeListener { + void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated); +} + class TreeItems : TreeItem { // signal handler OnTreeContentChangeListener - Signal!OnTreeContentChangeListener contentListener; - Signal!OnTreeStateChangeListener stateListener; + Listener!OnTreeContentChangeListener contentListener; + Listener!OnTreeStateChangeListener stateListener; + Listener!OnTreeSelectionChangeListener selectionListener; + protected TreeItem _selectedItem; this() { @@ -211,6 +219,18 @@ class TreeItems : TreeItem { _selectedItem = item; if (stateListener.assigned) stateListener(this); + if (selectionListener.assigned) + selectionListener(this, _selectedItem, false); + } + + override void activateItem(TreeItem item) { + if (!(_selectedItem is item)) { + _selectedItem = item; + if (stateListener.assigned) + stateListener(this); + } + if (selectionListener.assigned) + selectionListener(this, _selectedItem, true); } @property override TreeItem selectedItem() { @@ -219,6 +239,77 @@ class TreeItems : TreeItem { } +/// grid control action codes +enum TreeActions : int { + /// no action + None = 0, + /// move selection up + Up = 2000, + /// move selection down + Down, + /// move selection left + Left, + /// move selection right + Right, + + /// scroll up, w/o changing selection + ScrollUp, + /// scroll down, w/o changing selection + ScrollDown, + /// scroll left, w/o changing selection + ScrollLeft, + /// scroll right, w/o changing selection + ScrollRight, + + /// scroll top w/o changing selection + ScrollTop, + /// scroll bottom, w/o changing selection + ScrollBottom, + + /// scroll up, w/o changing selection + ScrollPageUp, + /// scroll down, w/o changing selection + ScrollPageDown, + /// scroll left, w/o changing selection + ScrollPageLeft, + /// scroll right, w/o changing selection + ScrollPageRight, + + /// move cursor one page up + PageUp, + /// move cursor one page up with selection + SelectPageUp, + /// move cursor one page down + PageDown, + /// move cursor one page down with selection + SelectPageDown, + /// move cursor to the beginning of page + PageBegin, + /// move cursor to the beginning of page with selection + SelectPageBegin, + /// move cursor to the end of page + PageEnd, + /// move cursor to the end of page with selection + SelectPageEnd, + /// move cursor to the beginning of line + LineBegin, + /// move cursor to the beginning of line with selection + SelectLineBegin, + /// move cursor to the end of line + LineEnd, + /// move cursor to the end of line with selection + SelectLineEnd, + /// move cursor to the beginning of document + DocumentBegin, + /// move cursor to the beginning of document with selection + SelectDocumentBegin, + /// move cursor to the end of document + DocumentEnd, + /// move cursor to the end of document with selection + SelectDocumentEnd, +} + + const int DOUBLE_CLICK_TIME_MS = 250; class TreeItemWidget : HorizontalLayout { @@ -238,33 +329,40 @@ class TreeItemWidget : HorizontalLayout { _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; + //dchar[] tabText; + //dchar[] singleTab = [' ', ' ', ' ', ' ']; + //for (int i = 1; i < _item.level; i++) + // tabText ~= singleTab; + //_tab.text = cast(dstring)tabText; + int w = (_item.level - 1) * style.font.size * 2; + _tab.minWidth = w; + _tab.maxWidth = w; if (_item.hasChildren) { _expander = new ImageWidget("expander", _item.hasChildren && _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"); - _expander.styleId = "TREE_ITEM_EXPANDER_ICON"; + _expander.styleId = "TREE_ITEM_EXPAND_ICON"; _expander.clickable = true; _expander.trackHover = true; - //_expander.setState(State.Parent); - _expander.onClickListener.connect(delegate(Widget source) { + + _expander.onClickListener = delegate(Widget source) { _item.selectItem(_item); _item.toggleExpand(_item); return true; - }); + }; } - onClickListener.connect(delegate(Widget source) { + onClickListener = delegate(Widget source) { long ts = currentTimeMillis(); _item.selectItem(_item); - if (_item.hasChildren && ts - lastClickTime < DOUBLE_CLICK_TIME_MS) { - _item.toggleExpand(_item); + if (ts - lastClickTime < DOUBLE_CLICK_TIME_MS) { + if (_item.hasChildren) { + _item.toggleExpand(_item); + } else { + _item.activateItem(_item); + } } lastClickTime = ts; return true; - }); + }; if (_item.iconRes.length > 0) { _icon = new ImageWidget("icon", _item.iconRes); _icon.styleId = "TREE_ITEM_ICON"; @@ -282,6 +380,28 @@ class TreeItemWidget : HorizontalLayout { addChild(_label); } + override bool onKeyEvent(KeyEvent event) { + if (onKeyListener.assigned && onKeyListener(this, event)) + return true; // processed by external handler + if (!focused || !visible) + return false; + if (event.action != KeyAction.KeyDown) + return false; + int action = 0; + switch (event.keyCode) { + case KeyCode.SPACE: + case KeyCode.RETURN: + if (_item.hasChildren) + _item.toggleExpand(_item); + else + _item.activateItem(_item); + return true; + default: + break; + } + return false; + } + void updateWidget() { if (_expander) { _expander.drawable = _item.expanded ? "arrow_right_down_black" : "arrow_right_hollow"; @@ -297,12 +417,14 @@ class TreeItemWidget : HorizontalLayout { } } -class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateChangeListener { +class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateChangeListener, OnTreeSelectionChangeListener, OnKeyHandler { protected TreeItems _tree; @property ref TreeItems items() { return _tree; } + Signal!OnTreeSelectionChangeListener selectionListener; + protected bool _needUpdateWidgets; protected bool _needUpdateWidgetStates; @@ -310,10 +432,30 @@ class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateCh super(ID, hscrollbarMode, vscrollbarMode); contentWidget = new VerticalLayout("TREE_CONTENT"); _tree = new TreeItems(); - _tree.contentListener.connect(this); - _tree.stateListener.connect(this); + _tree.contentListener = this; + _tree.stateListener = this; _needUpdateWidgets = true; _needUpdateWidgetStates = true; + acceleratorMap.add( [ + new Action(TreeActions.Up, KeyCode.UP, 0), + new Action(TreeActions.Down, KeyCode.DOWN, 0), + new Action(TreeActions.Left, KeyCode.LEFT, 0), + new Action(TreeActions.Right, KeyCode.RIGHT, 0), + new Action(TreeActions.LineBegin, KeyCode.HOME, 0), + new Action(TreeActions.LineEnd, KeyCode.END, 0), + new Action(TreeActions.PageUp, KeyCode.PAGEUP, 0), + new Action(TreeActions.PageDown, KeyCode.PAGEDOWN, 0), + new Action(TreeActions.PageBegin, KeyCode.PAGEUP, KeyFlag.Control), + new Action(TreeActions.PageEnd, KeyCode.PAGEDOWN, KeyFlag.Control), + new Action(TreeActions.ScrollTop, KeyCode.HOME, KeyFlag.Control), + new Action(TreeActions.ScrollBottom, KeyCode.END, KeyFlag.Control), + new Action(TreeActions.ScrollPageUp, KeyCode.PAGEUP, KeyFlag.Control), + new Action(TreeActions.ScrollPageDown, KeyCode.PAGEDOWN, KeyFlag.Control), + new Action(TreeActions.ScrollUp, KeyCode.UP, KeyFlag.Control), + new Action(TreeActions.ScrollDown, KeyCode.DOWN, KeyFlag.Control), + new Action(TreeActions.ScrollLeft, KeyCode.LEFT, KeyFlag.Control), + new Action(TreeActions.ScrollRight, KeyCode.RIGHT, KeyFlag.Control), + ]); } ~this() { @@ -325,7 +467,19 @@ class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateCh /** Override to use custom tree item widgets. */ protected Widget createItemWidget(TreeItem item) { - return new TreeItemWidget(item); + Widget res = new TreeItemWidget(item); + res.onKeyListener = this; + return res; + } + + override bool onKey(Widget source, KeyEvent event) { + if (event.action == KeyAction.KeyDown) { + Action action = findKeyAction(event.keyCode, event.flags & (KeyFlag.Shift | KeyFlag.Alt | KeyFlag.Control)); + if (action !is null) { + return handleAction(action); + } + } + return false; } protected void addWidgets(TreeItem item) { @@ -385,6 +539,43 @@ class TreeWidgetBase : ScrollWidget, OnTreeContentChangeListener, OnTreeStateCh requestLayout(); } + override void onTreeItemSelected(TreeItems source, TreeItem selectedItem, bool activated) { + if (selectionListener.assigned) + selectionListener(source, selectedItem, activated); + } + + override protected bool handleAction(const Action a) { + Log.d("tree.handleAction ", a.id); + switch (a.id) { + case TreeActions.ScrollLeft: + if (_hscrollbar) + _hscrollbar.sendScrollEvent(ScrollAction.LineUp); + break; + case TreeActions.ScrollRight: + if (_hscrollbar) + _hscrollbar.sendScrollEvent(ScrollAction.LineDown); + break; + case TreeActions.ScrollUp: + if (_vscrollbar) + _vscrollbar.sendScrollEvent(ScrollAction.LineUp); + break; + case TreeActions.ScrollPageUp: + if (_vscrollbar) + _vscrollbar.sendScrollEvent(ScrollAction.PageUp); + break; + case TreeActions.ScrollDown: + if (_vscrollbar) + _vscrollbar.sendScrollEvent(ScrollAction.LineDown); + break; + case TreeActions.ScrollPageDown: + if (_vscrollbar) + _vscrollbar.sendScrollEvent(ScrollAction.PageDown); + break; + default: + return super.handleAction(a); + } + return true; + } } class TreeWidget : TreeWidgetBase { diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 7094db0f..f70de433 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -774,8 +774,6 @@ class Widget { bool handleMoveFocusUsingKeys(KeyEvent event) { if (!focused || !visible) return false; - if (!visible) - return false; if (event.action != KeyAction.KeyDown) return false; FocusMovement direction = FocusMovement.None;