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).
new-script-api
scrawl 7 years ago
parent e7ad012977
commit c11fe6788f

@ -42,7 +42,7 @@ add_openmw_dir (mwgui
itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation
) )
add_openmw_dir (mwdialogue add_openmw_dir (mwdialogue

@ -7,6 +7,8 @@
#include <map> #include <map>
#include <set> #include <set>
#include <MyGUI_KeyCode.h>
#include "../mwgui/mode.hpp" #include "../mwgui/mode.hpp"
namespace Loading namespace Loading
@ -368,6 +370,8 @@ namespace MWBase
virtual void writeFog(MWWorld::CellStore* cell) = 0; virtual void writeFog(MWWorld::CellStore* cell) = 0;
virtual const MWGui::TextColours& getTextColours() = 0; virtual const MWGui::TextColours& getTextColours() = 0;
virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text) = 0;
}; };
} }

@ -0,0 +1,140 @@
#include "keyboardnavigation.hpp"
#include <MyGUI_InputManager.h>
#include <MyGUI_WidgetManager.h>
#include <MyGUI_Button.h>
#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<MyGUI::Widget*>& 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<int>(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<MyGUI::Button>(false);
//if (button && button->getEnabled())
if (focus->getTypeName().find("Button") != std::string::npos && focus->getEnabled())
{
focus->eventMouseButtonClick(focus);
return true;
}
return false;
}
}

@ -0,0 +1,27 @@
#ifndef OPENMW_MWGUI_KEYBOARDNAVIGATION_H
#define OPENMW_MWGUI_KEYBOARDNAVIGATION_H
#include <MyGUI_KeyCode.h>
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

@ -112,6 +112,7 @@
#include "controllers.hpp" #include "controllers.hpp"
#include "jailscreen.hpp" #include "jailscreen.hpp"
#include "itemchargeview.hpp" #include "itemchargeview.hpp"
#include "keyboardnavigation.hpp"
namespace namespace
{ {
@ -248,6 +249,8 @@ namespace MWGui
MyGUI::FactoryManager::getInstance().registerFactory<ResourceImageSetPointerFix>("Resource", "ResourceImageSetPointer"); MyGUI::FactoryManager::getInstance().registerFactory<ResourceImageSetPointerFix>("Resource", "ResourceImageSetPointer");
MyGUI::ResourceManager::getInstance().load("core.xml"); MyGUI::ResourceManager::getInstance().load("core.xml");
mKeyboardNavigation.reset(new KeyboardNavigation());
mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer);
//set up the hardware cursor manager //set up the hardware cursor manager
@ -433,6 +436,8 @@ namespace MWGui
WindowManager::~WindowManager() WindowManager::~WindowManager()
{ {
mKeyboardNavigation.reset();
MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); MyGUI::LanguageManager::getInstance().eventRequestTag.clear();
MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear();
MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear();
@ -2202,4 +2207,11 @@ namespace MWGui
return mTextColours; 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;
}
} }

@ -122,6 +122,7 @@ namespace MWGui
class ScreenFader; class ScreenFader;
class DebugWindow; class DebugWindow;
class JailScreen; class JailScreen;
class KeyboardNavigation;
class WindowManager : public MWBase::WindowManager class WindowManager : public MWBase::WindowManager
{ {
@ -397,6 +398,8 @@ namespace MWGui
virtual const MWGui::TextColours& getTextColours(); virtual const MWGui::TextColours& getTextColours();
virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text);
private: private:
const MWWorld::ESMStore* mStore; const MWWorld::ESMStore* mStore;
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
@ -522,6 +525,8 @@ namespace MWGui
MWGui::TextColours mTextColours; MWGui::TextColours mTextColours;
std::unique_ptr<KeyboardNavigation> 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. * 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: * Supported syntax:

@ -676,7 +676,7 @@ namespace MWInput
{ {
consumed = SDL_IsTextInputActive() && consumed = SDL_IsTextInputActive() &&
( !(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym)); // Little trick to check if key is printable ( !(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); setPlayerControlsEnabled(!guiFocus);
} }
if (arg.repeat) if (arg.repeat)

@ -71,6 +71,7 @@
<Resource type="ResourceSkin" name="MW_Button" size="136 24" version="3.2.1"> <Resource type="ResourceSkin" name="MW_Button" size="136 24" version="3.2.1">
<Property key="FontName" value="Default"/> <Property key="FontName" value="Default"/>
<Property key="TextAlign" value="Center"/> <Property key="TextAlign" value="Center"/>
<Property key="NeedKey" value="true"/>
<Child type="Widget" skin="BTN_Left" offset="0 4 4 16" align="VStretch Left"/> <Child type="Widget" skin="BTN_Left" offset="0 4 4 16" align="VStretch Left"/>
<Child type="Widget" skin="BTN_Right" offset="132 4 4 16" align="VStretch Right"/> <Child type="Widget" skin="BTN_Right" offset="132 4 4 16" align="VStretch Right"/>

Loading…
Cancel
Save