From e9085c774ba3d307a5fa5f9208bff7dedf1cdc0d Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 15 Apr 2014 17:15:29 +0400 Subject: [PATCH] key processing in menu --- src/dlangui/platforms/common/platform.d | 32 +++++++++++++++ src/dlangui/platforms/windows/winapp.d | 4 +- src/dlangui/widgets/controls.d | 2 + src/dlangui/widgets/lists.d | 45 +++++++++++++++++++++ src/dlangui/widgets/menu.d | 54 +++++++++++++++++++++++-- src/dlangui/widgets/widget.d | 36 +++++++++++++++++ 6 files changed, 168 insertions(+), 5 deletions(-) diff --git a/src/dlangui/platforms/common/platform.d b/src/dlangui/platforms/common/platform.d index d36ff4d8..59e16875 100644 --- a/src/dlangui/platforms/common/platform.d +++ b/src/dlangui/platforms/common/platform.d @@ -177,12 +177,44 @@ class Window { _mouseCaptureButtons = event.flags & (MouseFlag.LButton|MouseFlag.RButton|MouseFlag.MButton); } + protected Widget _focusedWidget; + /// returns current focused widget + @property Widget focusedWidget() { + if (!isChild(_focusedWidget)) + _focusedWidget = null; + return _focusedWidget; + } + + /// change focus to widget + Widget setFocus(Widget newFocus) { + if (!isChild(_focusedWidget)) + _focusedWidget = null; + Widget oldFocus = _focusedWidget; + if (oldFocus is newFocus) + return oldFocus; + if (oldFocus !is null) + oldFocus.resetState(State.Focused); + if (newFocus is null || isChild(newFocus)) { + _focusedWidget = newFocus; + if (_focusedWidget !is null) { + Log.d("new focus: ", _focusedWidget.id); + _focusedWidget.setState(State.Focused); + } + } + return _focusedWidget; + } + protected bool dispatchKeyEvent(Widget root, KeyEvent event) { return false; } /// dispatch keyboard event bool dispatchKeyEvent(KeyEvent event) { + Widget focus = focusedWidget; + if (focus !is null) { + if (focus.onKeyEvent(event)) + return true; // processed by focused widget + } return false; } diff --git a/src/dlangui/platforms/windows/winapp.d b/src/dlangui/platforms/windows/winapp.d index 79af617e..cac1548d 100644 --- a/src/dlangui/platforms/windows/winapp.d +++ b/src/dlangui/platforms/windows/winapp.d @@ -445,7 +445,7 @@ class Win32Window : Window { } else if (action == KeyAction.Text && character != 0) { dchar[] text; text ~= character; - event = new KeyEvent(action, 0, _keyFlags, text); + event = new KeyEvent(action, 0, _keyFlags, cast(dstring)text); } bool res = false; if (event !is null) { @@ -746,7 +746,7 @@ LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_KEYUP: if (window !is null) { int repeatCount = lParam & 0xFFFF; - if (window.onKey(message == WM_KEYDOWN ? KeyAction.KeyDown : KeyAction.KeyDown, wParam, repeatCount)) + if (window.onKey(message == WM_KEYDOWN ? KeyAction.KeyDown : KeyAction.KeyUp, wParam, repeatCount)) return 0; // processed return 0; } diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index 13ad4a61..3c1eae9e 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -134,6 +134,7 @@ class ImageButton : ImageWidget { super(ID); styleId = "BUTTON"; _drawableId = drawableId; + focusable = true; trackHover = true; } } @@ -146,6 +147,7 @@ class Button : Widget { this(string ID = null) { super(ID); styleId = "BUTTON"; + focusable = true; trackHover = true; } diff --git a/src/dlangui/widgets/lists.d b/src/dlangui/widgets/lists.d index 52360839..1b1891b6 100644 --- a/src/dlangui/widgets/lists.d +++ b/src/dlangui/widgets/lists.d @@ -161,6 +161,7 @@ class ListWidget : WidgetGroup, OnScrollHandler { this(string ID = null, Orientation orientation = Orientation.Vertical) { super(ID); _orientation = orientation; + focusable = true; _hoverItemIndex = -1; _selectedItemIndex = -1; _scrollbar = new ScrollBar("listscroll", orientation); @@ -469,6 +470,48 @@ class ListWidget : WidgetGroup, OnScrollHandler { } } + /// list navigation using keys + override bool onKeyEvent(KeyEvent event) { + if (itemCount == 0) + return false; + int navigationDelta = 0; + if (event.action == KeyAction.KeyDown) { + if (orientation == Orientation.Vertical) { + if (event.keyCode == KeyCode.DOWN) + navigationDelta = 1; + else if (event.keyCode == KeyCode.UP) + navigationDelta = -1; + } else { + if (event.keyCode == KeyCode.RIGHT) + navigationDelta = 1; + else if (event.keyCode == KeyCode.LEFT) + 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); + return true; + } + return false; + } + /// process mouse event; return true if event is processed by widget. override bool onMouseEvent(MouseEvent event) { //Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")"); @@ -486,6 +529,8 @@ class ListWidget : WidgetGroup, OnScrollHandler { } else { scrollOffset.x = _scrollPosition; } + if (event.action == MouseAction.ButtonDown && (event.flags & (MouseFlag.LButton || MouseFlag.RButton))) + setFocus(); for (int i = 0; i < itemCount; i++) { Rect itemrc = _itemRects[i]; itemrc.left += rc.left - scrollOffset.x; diff --git a/src/dlangui/widgets/menu.d b/src/dlangui/widgets/menu.d index d3f7b4a3..94fa7df7 100644 --- a/src/dlangui/widgets/menu.d +++ b/src/dlangui/widgets/menu.d @@ -106,9 +106,10 @@ class MenuWidgetBase : ListWidget { _openedPopup = null; _openedMenu = null; selectItem(-1); + window.setFocus(this); } - protected void openSubmenu(MenuItemWidget itemWidget) { + protected void openSubmenu(MenuItemWidget itemWidget, bool selectFirstItem) { if (_openedPopup !is null) { _openedPopup.close(); } @@ -118,6 +119,9 @@ class MenuWidgetBase : ListWidget { popup.flags = PopupFlags.CloseOnClickOutside; _openedPopup = popup; _openedMenu = popupMenu; + window.setFocus(popupMenu); + if (selectFirstItem) + _openedMenu.selectItem(0); } /// override to handle change of selection @@ -131,7 +135,7 @@ class MenuWidgetBase : ListWidget { if (itemWidget !is null) { if (itemWidget.item.isSubmenu()) { if (_selectOnHover) { - openSubmenu(itemWidget); + openSubmenu(itemWidget, false); } } else { // normal item @@ -161,6 +165,10 @@ class MenuWidgetBase : ListWidget { } } + @property MenuItemWidget selectedMenuItemWidget() { + return _selectedItemIndex >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(_selectedItemIndex) : null; + } + /// override to handle mouse up on item override protected void itemClicked(int index) { MenuItemWidget itemWidget = index >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(index) : null; @@ -175,7 +183,7 @@ class MenuWidgetBase : ListWidget { selectItem(-1); selectOnHover = false; } else { - openSubmenu(itemWidget); + openSubmenu(itemWidget, false); selectOnHover = true; } } else { @@ -184,6 +192,46 @@ class MenuWidgetBase : ListWidget { } } } + + /// returns popup this menu is located in + @property PopupWidget thisPopup() { + return cast(PopupWidget)parent; + } + + /// list navigation using keys + override bool onKeyEvent(KeyEvent event) { + // TODO: + if (orientation == Orientation.Horizontal) { + if (event.action == KeyAction.KeyDown) { + } + } 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(); + return true; + } + } + return true; + } else if (event.keyCode == KeyCode.RIGHT) { + MenuItemWidget thisItem = selectedMenuItemWidget(); + if (thisItem !is null && thisItem.item.isSubmenu) { + openSubmenu(thisItem, true); + return true; + } + return true; + } + } else if (event.action == KeyAction.KeyUp) { + if (event.keyCode == KeyCode.LEFT || event.keyCode == KeyCode.RIGHT) { + return true; + } + } + } + return super.onKeyEvent(event); + } + } /// main menu (horizontal) diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 22c948ff..1f3fd303 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -307,11 +307,42 @@ class Widget { return _pos.isPointInside(x, y); } + protected bool _focusable; + @property bool focusable() { return _focusable; } + @property Widget focusable(bool flg) { _focusable = flg; return this; } + @property bool focused() { + return (window !is null && window.focusedWidget is this && (state & State.Focused)); + } + /// sets focus to this widget, returns previously focused widget + Widget setFocus() { + if (window is null) + return null; + if (!_focusable) + return window.focusedWidget; + return window.setFocus(this); + } + // ======================================================= // Events /// process key event, return true if event is processed. bool onKeyEvent(KeyEvent event) { + if (_onClickListener !is null) { + // support onClick event initiated by Space or Return keys + if (event.action == KeyAction.KeyDown) { + if (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN) { + setState(State.Pressed); + return true; + } + } + if (event.action == KeyAction.KeyUp) { + if (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN) { + resetState(State.Pressed); + _onClickListener(this); + return true; + } + } + } return false; } @@ -322,6 +353,8 @@ class Widget { if (_onClickListener !is null) { if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { setState(State.Pressed); + if (focusable) + setFocus(); return true; } if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { @@ -339,6 +372,9 @@ class Widget { return true; } } + if (focusable && event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { + setFocus(); + } if (trackHover) { if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { if ((state & State.Hovered)) {