From c11fe6788fff68a73444c60409d6551f13678995 Mon Sep 17 00:00:00 2001 From: scrawl <720642+scrawl@users.noreply.github.com> Date: Fri, 22 Sep 2017 16:52:39 +0200 Subject: [PATCH] Add basic keyboard navigation for the GUI (Shift)Tab cycles, arrow keys move to the next button in that direction, Enter/Space accepts. Note: Unless MyGUI is hacked to bits, clicking on an empty space will annoyingly reset the key focus. Not sure how to deal with that yet. The visual highlight for selected buttons requires MyGUI commit 632d007429d0bf0c7d7f6c5db4a08353a63dd839 or later to appear (to be released in 3.2.3). --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/windowmanager.hpp | 4 + apps/openmw/mwgui/keyboardnavigation.cpp | 140 +++++++++++++++++++++++ apps/openmw/mwgui/keyboardnavigation.hpp | 27 +++++ apps/openmw/mwgui/windowmanagerimp.cpp | 12 ++ apps/openmw/mwgui/windowmanagerimp.hpp | 5 + apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- files/mygui/openmw_button.skin.xml | 1 + 8 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 apps/openmw/mwgui/keyboardnavigation.cpp create mode 100644 apps/openmw/mwgui/keyboardnavigation.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 00ae2fa4ab..43952afdb5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -42,7 +42,7 @@ add_openmw_dir (mwgui itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview - draganddrop timeadvancer jailscreen itemchargeview + draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 4560ab2700..37c3ce0d3c 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../mwgui/mode.hpp" namespace Loading @@ -368,6 +370,8 @@ namespace MWBase virtual void writeFog(MWWorld::CellStore* cell) = 0; virtual const MWGui::TextColours& getTextColours() = 0; + + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text) = 0; }; } diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp new file mode 100644 index 0000000000..840a12f1e0 --- /dev/null +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -0,0 +1,140 @@ +#include "keyboardnavigation.hpp" + +#include +#include +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" + +namespace MWGui +{ + +/// Recursively get all child widgets that accept keyboard input +void getKeyFocusWidgets(MyGUI::Widget* parent, std::vector& results) +{ + MyGUI::EnumeratorWidgetPtr enumerator = parent->getEnumerator(); + while (enumerator.next()) + { + MyGUI::Widget* w = enumerator.current(); + if (!w->getVisible() || !w->getEnabled()) + continue; + if (w->getNeedKeyFocus()) + results.push_back(w); + else + getKeyFocusWidgets(w, results); + } +} + +KeyboardNavigation::KeyboardNavigation() +{ +} + +KeyboardNavigation::~KeyboardNavigation() +{ +} + +bool isButtonFocus() +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + return focus->getTypeName().find("Button") != std::string::npos; +} + +enum Direction +{ + D_Left, + D_Up, + D_Right, + D_Down, + D_Next, + D_Prev +}; + +bool KeyboardNavigation::injectKeyPress(MyGUI::KeyCode key, unsigned int text) +{ + switch (key.getValue()) + { + case MyGUI::KeyCode::ArrowLeft: + return switchFocus(D_Left, false); + case MyGUI::KeyCode::ArrowRight: + return switchFocus(D_Right, false); + case MyGUI::KeyCode::ArrowUp: + return switchFocus(D_Up, false); + case MyGUI::KeyCode::ArrowDown: + return switchFocus(D_Down, false); + case MyGUI::KeyCode::Tab: + return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); + case MyGUI::KeyCode::Return: + case MyGUI::KeyCode::NumpadEnter: + case MyGUI::KeyCode::Space: + return accept(); + default: + return false; + } +} + +bool KeyboardNavigation::switchFocus(int direction, bool wrap) +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (!focus) + return false; + + if (!isButtonFocus() && direction != D_Prev && direction != D_Next) + return false; + + MyGUI::Widget* window = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + while (window->getParent()) + window = window->getParent(); + + MyGUI::VectorWidgetPtr keyFocusList; + getKeyFocusWidgets(window, keyFocusList); + + if (keyFocusList.empty()) + return false; + + MyGUI::VectorWidgetPtr::iterator found = std::find(keyFocusList.begin(), keyFocusList.end(), focus); + if (found == keyFocusList.end()) + return false; + + bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); + + int index = found - keyFocusList.begin(); + index = forward ? (index+1) : (index-1); + if (wrap) + index = (index + keyFocusList.size())%keyFocusList.size(); + else + index = std::min(std::max(0, index), static_cast(keyFocusList.size())-1); + + MyGUI::Widget* next = keyFocusList[index]; + int vertdiff = next->getTop() - focus->getTop(); + int horizdiff = next->getLeft() - focus->getLeft(); + if (direction == D_Right && horizdiff <= 0) + return false; + else if (direction == D_Left && horizdiff >= 0) + return false; + else if (direction == D_Down && vertdiff <= 0) + return false; + else if (direction == D_Up && vertdiff >= 0) + return false; + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(keyFocusList[index]); + return true; +} + +bool KeyboardNavigation::accept() +{ + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + if (!focus) + return false; + //MyGUI::Button* button = focus->castType(false); + //if (button && button->getEnabled()) + if (focus->getTypeName().find("Button") != std::string::npos && focus->getEnabled()) + { + focus->eventMouseButtonClick(focus); + return true; + } + return false; +} + + +} diff --git a/apps/openmw/mwgui/keyboardnavigation.hpp b/apps/openmw/mwgui/keyboardnavigation.hpp new file mode 100644 index 0000000000..86cc67962e --- /dev/null +++ b/apps/openmw/mwgui/keyboardnavigation.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_MWGUI_KEYBOARDNAVIGATION_H +#define OPENMW_MWGUI_KEYBOARDNAVIGATION_H + +#include + +namespace MWGui +{ + + class KeyboardNavigation + { + public: + KeyboardNavigation(); + ~KeyboardNavigation(); + + /// @return Was the key handled by this class? + bool injectKeyPress(MyGUI::KeyCode key, unsigned int text); + + private: + bool switchFocus(int direction, bool wrap); + + /// Send button press event to focused button + bool accept(); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1526949a3b..5d7a547350 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -112,6 +112,7 @@ #include "controllers.hpp" #include "jailscreen.hpp" #include "itemchargeview.hpp" +#include "keyboardnavigation.hpp" namespace { @@ -248,6 +249,8 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Resource", "ResourceImageSetPointer"); MyGUI::ResourceManager::getInstance().load("core.xml"); + mKeyboardNavigation.reset(new KeyboardNavigation()); + mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); //set up the hardware cursor manager @@ -433,6 +436,8 @@ namespace MWGui WindowManager::~WindowManager() { + mKeyboardNavigation.reset(); + MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); @@ -2202,4 +2207,11 @@ namespace MWGui return mTextColours; } + bool WindowManager::injectKeyPress(MyGUI::KeyCode key, unsigned int text) + { + if (!mKeyboardNavigation->injectKeyPress(key, text)) + return MyGUI::InputManager::getInstance().injectKeyPress(key, text); + else + return true; + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 4f06afb7d9..3e17046df0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -122,6 +122,7 @@ namespace MWGui class ScreenFader; class DebugWindow; class JailScreen; + class KeyboardNavigation; class WindowManager : public MWBase::WindowManager { @@ -397,6 +398,8 @@ namespace MWGui virtual const MWGui::TextColours& getTextColours(); + virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text); + private: const MWWorld::ESMStore* mStore; Resource::ResourceSystem* mResourceSystem; @@ -522,6 +525,8 @@ namespace MWGui MWGui::TextColours mTextColours; + std::unique_ptr mKeyboardNavigation; + /** * Called when MyGUI tries to retrieve a tag's value. Tags must be denoted in #{tag} notation and will be replaced upon setting a user visible text/property. * Supported syntax: diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cc41b3831b..33f1edb6cf 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -676,7 +676,7 @@ namespace MWInput { consumed = SDL_IsTextInputActive() && ( !(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym)); // Little trick to check if key is printable - bool guiFocus = MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); + bool guiFocus = MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); setPlayerControlsEnabled(!guiFocus); } if (arg.repeat) diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml index 5aee8de7ab..ac9bf042da 100644 --- a/files/mygui/openmw_button.skin.xml +++ b/files/mygui/openmw_button.skin.xml @@ -71,6 +71,7 @@ +