diff --git a/src/dlangui/core/signals.d b/src/dlangui/core/signals.d index fb3a801f..5a8e699f 100644 --- a/src/dlangui/core/signals.d +++ b/src/dlangui/core/signals.d @@ -3,7 +3,7 @@ module dlangui.core.signals; import std.traits; import dlangui.core.collections; -/// parameter is interface with single method +/// Single listener; parameter is interface with single method struct Listener(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == 1) { alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0])); alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0])); @@ -37,7 +37,7 @@ struct Listener(T1) if (is(T1 == interface) && __traits(allMembers, T1).length = alias get this; } -/// implicitly specified return and parameter types +/// Single listener; implicitly specified return and parameter types struct Listener(RETURN_T, T1...) { alias slot_t = RETURN_T delegate(T1); @@ -65,7 +65,7 @@ struct Listener(RETURN_T, T1...) alias get this; } -/// implicitly specified return and parameter types +/// Multiple listeners; implicitly specified return and parameter types struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == 1) { alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0])); alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0])); @@ -75,6 +75,7 @@ struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == final bool assigned() { return _listeners.length > 0; } + /// replace all previously assigned listeners with new one (if null passed, remove all listeners) final void opAssign(slot_t listener) { _listeners.clear(); _listeners ~= listener; @@ -116,7 +117,7 @@ struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == } } -/// implicitly specified return and parameter types +/// Multiple listeners; implicitly specified return and parameter types struct Signal(RETURN_T, T1...) { alias slot_t = RETURN_T delegate(T1); diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 37733bf3..eb1b64ed 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -90,6 +90,10 @@ struct Rect { bool isPointInside(int x, int y) { return x >= left && x < right && y >= top && y < bottom; } + /// this rectangle is completely inside rc + bool isInsideOf(Rect rc) { + return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom; + } } /// character glyph diff --git a/src/dlangui/widgets/lists.d b/src/dlangui/widgets/lists.d index 5c659932..87ce0d12 100644 --- a/src/dlangui/widgets/lists.d +++ b/src/dlangui/widgets/lists.d @@ -101,11 +101,11 @@ class ListWidget : WidgetGroup, OnScrollHandler { Rect itemRect(int index) { Rect res = itemRectNoScroll(index); if (_orientation == Orientation.Horizontal) { - res.left += _scrollPosition; - res.right += _scrollPosition; + res.left -= _scrollPosition; + res.right -= _scrollPosition; } else { - res.top += _scrollPosition; - res.bottom += _scrollPosition; + res.top -= _scrollPosition; + res.bottom -= _scrollPosition; } return res; } @@ -192,16 +192,91 @@ class ListWidget : WidgetGroup, OnScrollHandler { protected void itemClicked(int index) { } - protected void selectItem(int index) { - if (_selectedItemIndex == index) - return; + protected void updateSelectedItemFocus() { if (_selectedItemIndex != -1) { - _adapter.resetItemState(_selectedItemIndex, State.Selected); + if ((_adapter.itemState(_selectedItemIndex) & State.Focused) != (state & State.Focused)) { + if (state & State.Focused) + _adapter.setItemState(_selectedItemIndex, State.Focused); + else + _adapter.resetItemState(_selectedItemIndex, State.Focused); + invalidate(); + } + } + } + + /// override to handle focus changes + override protected void onFocusChange(bool focused) { + updateSelectedItemFocus(); + } + + /// ensure selected item is visible (scroll if necessary) + void makeSelectionVisible() { + if (_selectedItemIndex < 0) + return; // no selection + makeItemVisible(_selectedItemIndex); + } + + /// ensure item is visible + void makeItemVisible(int itemIndex) { + if (itemIndex < 0 || itemIndex >= itemCount) + return; // no selection + Rect viewrc = Rect(0, 0, _clientRc.width, _clientRc.height); + Rect scrolledrc = itemRect(itemIndex); + if (scrolledrc.isInsideOf(viewrc)) // completely visible + return; + int delta = 0; + if (_orientation == Orientation.Vertical) { + if (scrolledrc.top < viewrc.top) + delta = scrolledrc.top - viewrc.top; + else if (scrolledrc.bottom > viewrc.bottom) + delta = scrolledrc.bottom - viewrc.bottom; + } else { + if (scrolledrc.left < viewrc.left) + delta = scrolledrc.left - viewrc.left; + else if (scrolledrc.right > viewrc.right) + delta = scrolledrc.right - viewrc.right; + } + int newPosition = _scrollPosition + delta; + _scrollbar.position = newPosition; + _scrollPosition = newPosition; + invalidate(); + } + + /// move selection + void moveSelection(int direction, bool wrapAround = true) { + if (itemCount <= 0) + return; + if (_selectedItemIndex < 0) { + // no previous selection + if (direction > 0) + selectItem(wrapAround ? 0 : itemCount - 1); + else + selectItem(wrapAround ? itemCount - 1 : 0); + return; + } + int newIndex = _selectedItemIndex + direction; + if (newIndex < 0) + newIndex = wrapAround ? itemCount - 1 : 0; + else if (newIndex >= itemCount) + newIndex = wrapAround ? 0 : itemCount - 1; + if (newIndex != _selectedItemIndex) + selectItem(newIndex); + } + + protected void selectItem(int index) { + if (_selectedItemIndex == index) { + updateSelectedItemFocus(); + makeSelectionVisible(); + return; + } + if (_selectedItemIndex != -1) { + _adapter.resetItemState(_selectedItemIndex, State.Selected | State.Focused); invalidate(); } _selectedItemIndex = index; if (_selectedItemIndex != -1) { - _adapter.setItemState(_selectedItemIndex, State.Selected); + makeSelectionVisible(); + _adapter.setItemState(_selectedItemIndex, State.Selected | (state & State.Focused)); invalidate(); } } @@ -488,28 +563,34 @@ class ListWidget : WidgetGroup, OnScrollHandler { navigationDelta = -1; } } - if (_selectedItemIndex != -1 && event.action == KeyAction.KeyUp && (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN)) { - itemClicked(_selectedItemIndex); - return true; - } if (navigationDelta != 0) { - int p = _selectedItemIndex; - if (p < 0) { - if (navigationDelta < 0) - p = itemCount - 1; - else - p = 0; - } else { - p += navigationDelta; - if (p < 0) - p = itemCount - 1; - else if (p >= itemCount) - p = 0; - } - selectItem(p); + moveSelection(navigationDelta); return true; } return false; + //if (_selectedItemIndex != -1 && event.action == KeyAction.KeyUp && (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN)) { + // itemClicked(_selectedItemIndex); + // return true; + //} + //if (navigationDelta != 0) { + // int p = _selectedItemIndex; + // if (p < 0) { + // if (navigationDelta < 0) + // p = itemCount - 1; + // else + // p = 0; + // } else { + // p += navigationDelta; + // if (p < 0) + // p = itemCount - 1; + // else if (p >= itemCount) + // p = 0; + // } + // setHoverItem(-1); + // selectItem(p); + // return true; + //} + //return false; } /// process mouse event; return true if event is processed by widget. diff --git a/src/dlangui/widgets/menu.d b/src/dlangui/widgets/menu.d index 94fa7df7..6d453ac1 100644 --- a/src/dlangui/widgets/menu.d +++ b/src/dlangui/widgets/menu.d @@ -106,6 +106,7 @@ class MenuWidgetBase : ListWidget { _openedPopup = null; _openedMenu = null; selectItem(-1); + setHoverItem(-1); window.setFocus(this); } @@ -154,6 +155,7 @@ class MenuWidgetBase : ListWidget { // top level handling Log.d("onMenuItem ", item.id); selectItem(-1); + setHoverItem(-1); selectOnHover = false; bool delegate(MenuItem item) listener = _onMenuItemClickListener; PopupWidget popup = cast(PopupWidget)parent; @@ -207,10 +209,16 @@ class MenuWidgetBase : ListWidget { } else { if (event.action == KeyAction.KeyDown) { if (event.keyCode == KeyCode.LEFT) { - if (_parentMenu !is null && _parentMenu.orientation == Orientation.Vertical) { - if (thisPopup !is null) { - // back to parent menu on Left key - thisPopup.close(); + if (_parentMenu !is null) { + if (_parentMenu.orientation == Orientation.Vertical) { + if (thisPopup !is null) { + // back to parent menu on Left key + thisPopup.close(); + return true; + } + } else { + // parent is main menu + _parentMenu.moveSelection(-1); return true; } } @@ -220,6 +228,9 @@ class MenuWidgetBase : ListWidget { if (thisItem !is null && thisItem.item.isSubmenu) { openSubmenu(thisItem, true); return true; + } else if (_parentMenu !is null && _parentMenu.orientation == Orientation.Horizontal) { + _parentMenu.moveSelection(1); + return true; } return true; } diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index e67d13d8..cf3dce6e 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -9,7 +9,8 @@ public import dlangui.graphics.resources; public import dlangui.graphics.fonts; public import dlangui.core.i18n; -public import std.signals; +//public import std.signals; +public import dlangui.core.signals; import dlangui.platforms.common.platform; @@ -135,12 +136,21 @@ class Widget { return _parent.state; return _state; } + /// override to handle focus changes + protected void onFocusChange(bool focused) { + } /// set new widget state (set of flags from State enum) @property Widget state(uint newState) { if (newState != _state) { + uint oldState = _state; _state = newState; // need to redraw invalidate(); + // notify focus changes + if ((oldState & State.Focused) && !(newState & State.Focused)) + onFocusChange(false); + else if (!(oldState & State.Focused) && (newState & State.Focused)) + onFocusChange(true); } return this; } @@ -327,7 +337,7 @@ class Widget { /// process key event, return true if event is processed. bool onKeyEvent(KeyEvent event) { - if (_onClickListener !is null) { + if (onClickListener.assigned) { // support onClick event initiated by Space or Return keys if (event.action == KeyAction.KeyDown) { if (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN) { @@ -338,7 +348,7 @@ class Widget { if (event.action == KeyAction.KeyUp) { if (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN) { resetState(State.Pressed); - _onClickListener(this); + onClickListener(this); return true; } } @@ -350,7 +360,7 @@ class Widget { bool onMouseEvent(MouseEvent event) { //Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")"); // support onClick - if (_onClickListener !is null) { + if (onClickListener.assigned) { if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { setState(State.Pressed); if (focusable) @@ -359,7 +369,7 @@ class Widget { } if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { resetState(State.Pressed); - _onClickListener(this); + onClickListener(this); return true; } if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { @@ -399,11 +409,9 @@ class Widget { return false; } - protected onClick_t _onClickListener; + /// on click event listener (bool delegate(Widget)) - @property onClick_t onClickListener() { return _onClickListener; } - /// set on click event listener (bool delegate(Widget)) - @property Widget onClickListener(onClick_t listener) { _onClickListener = listener; return this; } + Signal!OnClickHandler onClickListener; // ======================================================= // Layout and measurement methods