From 3e581571f48e6de8d065f499d79025f02a1a2e0b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 14 Nov 2020 19:13:16 +0100 Subject: [PATCH] QWERTY implementation of a virtual keyboard. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/windowmanager.hpp | 1 + apps/openmw/mwgui/windowmanagerimp.cpp | 22 +- apps/openmw/mwgui/windowmanagerimp.hpp | 4 + apps/openmw/mwvr/vrgui.cpp | 56 ++++ apps/openmw/mwvr/vrgui.hpp | 5 + apps/openmw/mwvr/vrinputmanager.cpp | 57 ++-- apps/openmw/mwvr/vrvirtualkeyboard.cpp | 273 ++++++++++++++++++ apps/openmw/mwvr/vrvirtualkeyboard.hpp | 79 +++++ apps/openmw/mwworld/worldimp.cpp | 2 +- components/CMakeLists.txt | 2 +- components/widgets/box.cpp | 33 +++ components/widgets/box.hpp | 12 +- components/widgets/numericeditbox.hpp | 4 +- components/widgets/virtualkeyboardmanager.cpp | 4 + components/widgets/virtualkeyboardmanager.hpp | 18 ++ files/mygui/CMakeLists.txt | 1 + files/mygui/openmw_layers_vr.xml | 1 + files/mygui/openmw_vr_virtual_keyboard.layout | 8 + 19 files changed, 532 insertions(+), 52 deletions(-) create mode 100644 apps/openmw/mwvr/vrvirtualkeyboard.cpp create mode 100644 apps/openmw/mwvr/vrvirtualkeyboard.hpp create mode 100644 components/widgets/virtualkeyboardmanager.cpp create mode 100644 components/widgets/virtualkeyboardmanager.hpp create mode 100644 files/mygui/openmw_vr_virtual_keyboard.layout diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 583c4db5c..7483d8b87 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -249,7 +249,7 @@ if(BUILD_OPENMW_VR) add_openmw_dir (mwvr openxraction openxractionset openxrdebug openxrinput openxrmanager openxrmanagerimpl openxrswapchain openxrswapchainimpl realisticcombat - vranimation vrcamera vrenvironment vrframebuffer vrgui vrinputmanager vrinput vrmetamenu vrsession vrshadow vrtypes vrview vrviewer + vranimation vrcamera vrenvironment vrframebuffer vrgui vrinputmanager vrinput vrmetamenu vrsession vrshadow vrtypes vrview vrviewer vrvirtualkeyboard ) openmw_add_executable(openmw_vr diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 849403421..07405b11f 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -107,6 +107,7 @@ namespace MWBase /// @note This method will block until the video finishes playing /// (and will continually update the window while doing so) virtual void playVideo(const std::string& name, bool allowSkipping) = 0; + virtual bool isPlayingVideo(void) const = 0; virtual void setNewGame(bool newgame) = 0; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index c794abb62..7a75b092e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -123,6 +123,7 @@ #include "../mwvr/vrmetamenu.hpp" #include "../mwvr/vrenvironment.hpp" #include "../mwvr/vrgui.hpp" +#include "../mwvr/vrvirtualkeyboard.hpp" #endif namespace MWGui @@ -168,6 +169,8 @@ namespace MWGui , mScreenFader(nullptr) , mDebugWindow(nullptr) , mJailScreen(nullptr) + , mVrMetaMenu(nullptr) + , mVirtualKeyboardManager(nullptr) , mTranslationDataStorage (translationDataStorage) , mCharGen(nullptr) , mInputBlocker(nullptr) @@ -308,6 +311,14 @@ namespace MWGui mDragAndDrop = new DragAndDrop(); +#ifdef USE_OPENXR + mVrMetaMenu = new MWVR::VrMetaMenu(w, h); + mWindows.push_back(mVrMetaMenu); + mGuiModeStates[GM_VrMetaMenu] = GuiModeState(mVrMetaMenu); + + mVirtualKeyboardManager = new MWVR::VirtualKeyboardManager; +#endif + Recharge* recharge = new Recharge(); mGuiModeStates[GM_Recharge] = GuiModeState(recharge); mWindows.push_back(recharge); @@ -448,12 +459,6 @@ namespace MWGui mWindows.push_back(mJailScreen); mGuiModeStates[GM_Jail] = GuiModeState(mJailScreen); -#ifdef USE_OPENXR - mVrMetaMenu = new MWVR::VrMetaMenu(w, h); - mWindows.push_back(mVrMetaMenu); - mGuiModeStates[GM_VrMetaMenu] = GuiModeState(mVrMetaMenu); -#endif - std::string werewolfFaderTex = "textures\\werewolfoverlay.dds"; if (mResourceSystem->getVFS()->exists(werewolfFaderTex)) { @@ -1835,6 +1840,11 @@ namespace MWGui mVideoEnabled = false; } + bool WindowManager::isPlayingVideo(void) const + { + return mVideoEnabled; + } + void WindowManager::sizeVideo(int screenWidth, int screenHeight) { // Use black bars to correct aspect ratio diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 201b64d79..051fc0e4b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -81,6 +81,7 @@ namespace osgMyGUI namespace Gui { class FontLoader; + class VirtualKeyboardManager; } namespace MWRender @@ -153,6 +154,7 @@ namespace MWGui /// @note This method will block until the video finishes playing /// (and will continually update the window while doing so) void playVideo(const std::string& name, bool allowSkipping) override; + bool isPlayingVideo(void) const override; /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. void setKeyFocusWidget (MyGUI::Widget* widget) override; @@ -447,6 +449,8 @@ namespace MWGui JailScreen* mJailScreen; MWVR::VrMetaMenu* mVrMetaMenu; + Gui::VirtualKeyboardManager* mVirtualKeyboardManager; + std::vector mWindows; Translation::Storage& mTranslationDataStorage; diff --git a/apps/openmw/mwvr/vrgui.cpp b/apps/openmw/mwvr/vrgui.cpp index fd4d13838..d420bf0dc 100644 --- a/apps/openmw/mwvr/vrgui.cpp +++ b/apps/openmw/mwvr/vrgui.cpp @@ -480,6 +480,22 @@ namespace MWVR LayerConfig gMessageBoxConfig = createDefaultConfig(6, false, SizingMode::Auto);; LayerConfig gNotificationConfig = createDefaultConfig(7, false, SizingMode::Fixed); + //LayerConfig gVirtualKeyboardConfig = createDefaultConfig(50); + LayerConfig gVirtualKeyboardConfig = LayerConfig{ + 10, + false, + osg::Vec4{0.f,0.f,0.f,.75f}, + osg::Vec3(0.025f,.025f,.066f), // offset (meters) + osg::Vec2(0.f,0.5f), // center (model space) + osg::Vec2(.25f, .25f), // extent (meters) + 2048, // Spatial resolution (pixels per meter) + osg::Vec2i(2048,2048), // Texture resolution + osg::Vec2(1,1), + SizingMode::Auto, + TrackingMode::HudLeftHand, + "" + }; + static const float sSideBySideRadius = 1.f; static const float sSideBySideAzimuthInterval = -osg::PI_4; static const LayerConfig createSideBySideConfig(int priority) @@ -559,6 +575,7 @@ namespace MWVR {"InputBlocker", gVideoPlayerConfig}, {"Menu", gVideoPlayerConfig}, {"LoadingScreen", gLoadingScreenConfig}, + {"VirtualKeyboard", gVirtualKeyboardConfig}, }; static std::set layerBlacklist = @@ -806,6 +823,30 @@ namespace MWVR } } + void VRGUIManager::setFocusWidget(MyGUI::Widget* widget) + { + // TODO: This relies on MyGUI internal functions and may break on any future version. + if (widget == mFocusWidget) + return; + if (mFocusWidget) + mFocusWidget->_riseMouseLostFocus(widget); + if (widget) + widget->_riseMouseSetFocus(mFocusWidget); + mFocusWidget = widget; + } + + bool VRGUIManager::injectMouseClick(bool onPress) + { + // TODO: This relies on MyGUI internal functions and may break on any future version. + if (mFocusWidget) + { + if(onPress) + mFocusWidget->_riseMouseButtonClick(); + return true; + } + return false; + } + void VRGUIManager::computeGuiCursor(osg::Vec3 hitPoint) { float x = 0; @@ -830,6 +871,21 @@ namespace MWVR MyGUI::InputManager::getInstance().injectMouseMove((int)x, (int)y, 0); MWBase::Environment::get().getWindowManager()->setCursorActive(true); + + // The virtual keyboard must be interactive regardless of modals + // This could be generalized with another config entry, but i don't think any other + // widgets/layers need it so i'm hardcoding it for the VirtualKeyboard for now. + if ( + mFocusLayer + && mFocusLayer->mLayerName == "VirtualKeyboard" + && MyGUI::InputManager::getInstance().isModalAny()) + { + auto* widget = MyGUI::LayerManager::getInstance().getWidgetFromPoint((int)x, (int)y); + setFocusWidget(widget); + } + else + setFocusWidget(nullptr); + } } diff --git a/apps/openmw/mwvr/vrgui.hpp b/apps/openmw/mwvr/vrgui.hpp index 83c70e535..d32edb4ff 100644 --- a/apps/openmw/mwvr/vrgui.hpp +++ b/apps/openmw/mwvr/vrgui.hpp @@ -159,12 +159,16 @@ namespace MWVR /// Gui cursor coordinates to use to simulate a mouse press/move if the player is currently pointing at a vr gui layer osg::Vec2i guiCursor() { return mGuiCursor; }; + /// Inject mouse click if applicable + bool injectMouseClick(bool onPress); + private: void computeGuiCursor(osg::Vec3 hitPoint); void updateSideBySideLayers(); void insertWidget(MWGui::Layout* widget); void removeWidget(MWGui::Layout* widget); void setFocusLayer(VRGUILayer* layer); + void setFocusWidget(MyGUI::Widget* widget); osg::ref_ptr mOsgViewer{ nullptr }; @@ -178,6 +182,7 @@ namespace MWVR Pose mHeadPose{}; osg::Vec2i mGuiCursor{}; VRGUILayer* mFocusLayer{ nullptr }; + MyGUI::Widget* mFocusWidget{ nullptr }; osg::observer_ptr mCamera{ nullptr }; }; } diff --git a/apps/openmw/mwvr/vrinputmanager.cpp b/apps/openmw/mwvr/vrinputmanager.cpp index 290435f4c..310c79f54 100644 --- a/apps/openmw/mwvr/vrinputmanager.cpp +++ b/apps/openmw/mwvr/vrinputmanager.cpp @@ -141,6 +141,9 @@ namespace MWVR void VRInputManager::injectMousePress(int sdlButton, bool onPress) { + if (Environment::get().getGUIManager()->injectMouseClick(onPress)) + return; + SDL_MouseButtonEvent arg; if (onPress) mMouseManager->mousePressed(arg, sdlButton); @@ -212,19 +215,15 @@ namespace MWVR {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, {MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - //{MWInput::A_ToggleHUD, "/user/hand/left/input/thumbstick/click"}, {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, {MWInput::A_ToggleSpell, "/user/hand/left/input/x/click"}, - //{MWInput::A_QuickSave, "/user/hand/left/input/y/click"}, {MWInput::A_Rest, "/user/hand/left/input/y/click"}, {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - //{MWInput::A_Journal, "/user/hand/right/input/b/click"}, {A_ActivateTouch, "/user/hand/right/input/squeeze/value"}, {MWInput::A_Activate, "/user/hand/right/input/squeeze/value"}, {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, {MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_ToggleDebug, "/user/hand/right/input/thumbstick/click"}, {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, }; @@ -255,17 +254,13 @@ namespace MWVR {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, {MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - //{MWInput::A_ToggleHUD, "/user/hand/left/input/thumbstick/click"}, {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, {MWInput::A_ToggleSpell, "/user/hand/left/input/x/click"}, - //{MWInput::A_QuickSave, "/user/hand/left/input/y/click"}, {MWInput::A_Rest, "/user/hand/left/input/y/click"}, {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - //{MWInput::A_Journal, "/user/hand/right/input/b/click"}, {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, {MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_ToggleDebug, "/user/hand/right/input/thumbstick/click"}, {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, {A_ActivateTouch, "/user/hand/right/input/squeeze/value"}, {MWInput::A_Activate, "/user/hand/right/input/squeeze/value"}, @@ -294,21 +289,15 @@ namespace MWVR // In-game character controls SuggestedBindings gameplayBindings{ - //{MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - //{MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_ToggleDebug, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_ToggleHUD, "/user/hand/left/input/thumbstick/click"}, {A_Recenter, "/user/hand/left/input/menu/click"}, {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, - //{MWInput::A_QuickSave, "/user/hand/left/input/thumbstick/click"}, {MWInput::A_Rest, "/user/hand/left/input/thumbstick/click"}, {MWInput::A_ToggleSpell, "/user/hand/left/input/trackpad/click"}, {MWInput::A_Sneak, "/user/hand/left/input/squeeze/click"}, {MWInput::A_Inventory, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_Journal, "/user/hand/right/input/thumbstick/click"}, {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, {MWInput::A_ToggleWeapon, "/user/hand/right/input/trackpad/click"}, {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, @@ -336,23 +325,17 @@ namespace MWVR std::string controllerProfilePath = "/interaction_profiles/valve/index_controller"; // In-game character controls SuggestedBindings gameplayBindings{ - //{MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, {MWInput::A_ToggleSpell, "/user/hand/left/input/a/click"}, {MWInput::A_Rest, "/user/hand/left/input/b/click"}, - //{MWInput::A_QuickSave, "/user/hand/left/input/b/click"}, {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, - //{MWInput::A_ToggleHUD, "/user/hand/left/input/thumbstick/click"}, {A_Recenter, "/user/hand/left/input/trackpad/force"}, {A_VrMetaMenu, "/user/hand/left/input/trackpad/force"}, {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, {MWInput::A_Sneak, "/user/hand/left/input/squeeze/force"}, {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - //{MWInput::A_Journal, "/user/hand/right/input/b/click"}, - //{MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, - //{MWInput::A_ToggleDebug, "/user/hand/right/input/thumbstick/click"}, {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, {A_ActivateTouch, "/user/hand/right/input/squeeze/force"}, {MWInput::A_Activate, "/user/hand/right/input/squeeze/force"}, @@ -377,18 +360,8 @@ namespace MWVR { std::string controllerProfilePath = "/interaction_profiles/htc/vive_controller"; - // TODO: I Didn't realize the vive wands were so bad. We don't have NEARLY enough actions available. - // In-game character controls SuggestedBindings gameplayBindings{ - //{MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - //{MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - //{MWInput::A_Journal, "/user/hand/right/input/b/click"}, - //{MWInput::A_QuickSave, "/user/hand/left/input/b/click"}, - //{MWInput::A_Rest, "/user/hand/left/input/b/click"}, - //{MWInput::A_ToggleDebug, "/user/hand/right/input/thumbstick/click"}, - //{MWInput::A_ToggleHUD, "/user/hand/left/input/thumbstick/click"}, {A_Recenter, "/user/hand/left/input/menu/click"}, {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, {A_VrMetaMenu, "/user/hand/right/input/squeeze/click"}, @@ -556,19 +529,23 @@ namespace MWVR { static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); auto* vrGuiManager = Environment::get().getGUIManager(); + auto* wm = MWBase::Environment::get().getWindowManager(); // OpenMW does not currently provide any way to directly request skipping a video. // This is copied from the controller manager and is used to skip videos, // and works because mygui only consumes the escape press if a video is currently playing. - auto kc = MWInput::sdlKeyToMyGUI(SDLK_ESCAPE); - if (action->onActivate()) - { - mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); - } - else if (action->onDeactivate()) + if (wm->isPlayingVideo()) { - mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); + auto kc = MWInput::sdlKeyToMyGUI(SDLK_ESCAPE); + if (action->onActivate()) + { + mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyPress(kc, 0)); + } + else if (action->onDeactivate()) + { + mBindingsManager->setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(kc)); + } } if (disableControls) @@ -623,14 +600,14 @@ namespace MWVR vrGuiManager->updateTracking(); break; case A_MenuSelect: - if (!MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Space, 0, 0)) + if (!wm->injectKeyPress(MyGUI::KeyCode::Return, 0, false)) executeAction(MWInput::A_Activate); break; case A_MenuBack: if (MyGUI::InputManager::getInstance().isModalAny()) - MWBase::Environment::get().getWindowManager()->exitCurrentModal(); + wm->exitCurrentModal(); else - MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + wm->exitCurrentGuiMode(); break; case MWInput::A_Use: pointActivation(true); diff --git a/apps/openmw/mwvr/vrvirtualkeyboard.cpp b/apps/openmw/mwvr/vrvirtualkeyboard.cpp new file mode 100644 index 000000000..31f20b5e8 --- /dev/null +++ b/apps/openmw/mwvr/vrvirtualkeyboard.cpp @@ -0,0 +1,273 @@ +#include "vrvirtualkeyboard.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/statemanager.hpp" + +namespace MWVR +{ + VirtualKeyboardManager::VirtualKeyboardManager() + : mVk(new VrVirtualKeyboard) + { + + } + + void VirtualKeyboardManager::registerEditBox(MyGUI::EditBox* editBox) + { + IDelegate* onSetFocusDelegate = newDelegate(mVk.get(), &VrVirtualKeyboard::delegateOnSetFocus); + IDelegate* onLostFocusDelegate = newDelegate(mVk.get(), &VrVirtualKeyboard::delegateOnLostFocus); + editBox->eventKeySetFocus += onSetFocusDelegate; + editBox->eventKeyLostFocus += onLostFocusDelegate; + + mDelegates[editBox] = Delegates(onSetFocusDelegate, onLostFocusDelegate); + } + + void VirtualKeyboardManager::unregisterEditBox(MyGUI::EditBox* editBox) + { + auto it = mDelegates.find(editBox); + if (it != mDelegates.end()) + { + editBox->eventKeySetFocus -= it->second.first; + editBox->eventKeyLostFocus -= it->second.second; + mDelegates.erase(it); + } + } + + + static const char* mClassTypeName; + + VrVirtualKeyboard::VrVirtualKeyboard() + : WindowBase("openmw_vr_virtual_keyboard.layout") + , mButtonBox(nullptr) + , mTarget(nullptr) + , mButtons() + , mShift(false) + , mCaps(false) + { + getWidget(mButtonBox, "ButtonBox"); + mMainWidget->setNeedKeyFocus(false); + mButtonBox->setNeedKeyFocus(false); + updateMenu(); + } + + VrVirtualKeyboard::~VrVirtualKeyboard() + { + + } + + void VrVirtualKeyboard::onResChange(int w, int h) + { + updateMenu(); + } + + void VrVirtualKeyboard::onFrame(float dt) + { + } + + void VrVirtualKeyboard::open(MyGUI::EditBox* target) + { + updateMenu(); + + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(target); + mTarget = target; + setVisible(true); + } + + void VrVirtualKeyboard::close() + { + setVisible(false); + mTarget = nullptr; + } + + void VrVirtualKeyboard::delegateOnSetFocus(MyGUI::Widget* _sender, MyGUI::Widget* _old) + { + open(static_cast(_sender)); + } + + void VrVirtualKeyboard::delegateOnLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new) + { + close(); + } + + void VrVirtualKeyboard::onButtonClicked(MyGUI::Widget* sender) + { + assert(mTarget); + MyGUI::InputManager::getInstance().setKeyFocusWidget(mTarget); + + std::string name = *sender->getUserData(); + + if (name == "Esc") + onEsc(); + if (name == "Tab") + onTab(); + if (name == "Caps") + onCaps(); + if (name == "Shift") + onShift(); + else + mShift = false; + if (name == "Back") + onBackspace(); + if (name == "Return") + onReturn(); + if (name == "Space") + textInput(" "); + if (name == "->") + textInput("->"); + if (name.length() == 1) + textInput(name); + + updateMenu(); + } + + void VrVirtualKeyboard::textInput(const std::string& symbol) + { + MyGUI::UString ustring(symbol); + MyGUI::UString::utf32string utf32string = ustring.asUTF32(); + for (MyGUI::UString::utf32string::const_iterator it = utf32string.begin(); it != utf32string.end(); ++it) + MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it); + } + + void VrVirtualKeyboard::onEsc() + { + close(); + } + + void VrVirtualKeyboard::onTab() + { + MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Tab); + MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Tab); + } + + void VrVirtualKeyboard::onCaps() + { + mCaps = !mCaps; + } + + void VrVirtualKeyboard::onShift() + { + mShift = !mShift; + } + + void VrVirtualKeyboard::onBackspace() + { + MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Backspace); + MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Backspace); + } + + void VrVirtualKeyboard::onReturn() + { + MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Return); + MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Return); + } + + bool VrVirtualKeyboard::exit() + { + close(); + return true; + } + + void VrVirtualKeyboard::updateMenu() + { + // TODO: Localization? + static std::vector row1{ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" }; + static std::vector row2{ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "Return" }; + static std::vector row3{ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\\", "->" }; + static std::vector row4{ "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Space" }; + std::map shiftMap; + shiftMap["1"] = "!"; + shiftMap["2"] = "@"; + shiftMap["3"] = "#"; + shiftMap["4"] = "$"; + shiftMap["5"] = "%"; + shiftMap["6"] = "^"; + shiftMap["7"] = "&"; + shiftMap["8"] = "*"; + shiftMap["9"] = "("; + shiftMap["0"] = ")"; + shiftMap["-"] = "_"; + shiftMap["="] = "+"; + shiftMap["\\"] = "|"; + shiftMap[","] = "<"; + shiftMap["."] = ">"; + shiftMap["/"] = "?"; + shiftMap[";"] = ":"; + shiftMap["'"] = "\""; + shiftMap["["] = "{"; + shiftMap["]"] = "}"; + shiftMap["`"] = "~"; + + std::vector< std::vector< std::string > > rows{ row1, row2, row3, row4 }; + + int sideSize = 50; + int margin = 10; + int xmax = 0; + int ymax = 0; + + if (mButtons.empty()) + { + int y = margin; + for (auto& row : rows) + { + int x = margin; + for (std::string& buttonId : row) + { + int width = sideSize + 10 * (buttonId.length() - 1); + MyGUI::Button* button = mButtonBox->createWidget( + "MW_Button", MyGUI::IntCoord(x, y, width, sideSize), MyGUI::Align::Default, buttonId); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &VrVirtualKeyboard::onButtonClicked); + button->setUserData(std::string(buttonId)); + button->setVisible(true); + button->setFontHeight(32); + button->setCaption(buttonId); + button->setNeedKeyFocus(false); + mButtons[buttonId] = button; + x += width + margin; + } + y += sideSize + margin; + } + } + + for (auto& row : rows) + { + for (std::string& buttonId : row) + { + auto* button = mButtons[buttonId]; + xmax = std::max(xmax, button->getAbsoluteRect().right); + ymax = std::max(ymax, button->getAbsoluteRect().bottom); + + if (buttonId.length() == 1) + { + auto caption = buttonId; + if (mShift ^ mCaps) + caption[0] = std::toupper(caption[0]); + else + caption[0] = std::tolower(caption[0]); + button->setCaption(caption); + button->setUserData(caption); + } + + if (mShift) + { + auto it = shiftMap.find(buttonId); + if (it != shiftMap.end()) + { + button->setCaption(it->second); + button->setUserData(it->second); + } + } + } + } + + std::cout << xmax << ", " << ymax << std::endl; + + setCoord(0, 0, xmax + margin, ymax + margin); + mButtonBox->setCoord(0, 0, xmax + margin, ymax + margin); + //mButtonBox->setCoord (margin, margin, width, height); + mButtonBox->setVisible(true); + } +} diff --git a/apps/openmw/mwvr/vrvirtualkeyboard.hpp b/apps/openmw/mwvr/vrvirtualkeyboard.hpp new file mode 100644 index 000000000..bdb0ea39f --- /dev/null +++ b/apps/openmw/mwvr/vrvirtualkeyboard.hpp @@ -0,0 +1,79 @@ +#ifndef OPENMW_GAME_MWVR_VRVIRTUALKEYBOARD_H +#define OPENMW_GAME_MWVR_VRVIRTUALKEYBOARD_H + +#include "../mwgui/windowbase.hpp" + +#include +#include "components/widgets/virtualkeyboardmanager.hpp" + +#include + +namespace Gui +{ + class VirtualKeyboardManager; +} + +namespace MWVR +{ + class VrVirtualKeyboard : public MWGui::WindowBase + { + public: + + VrVirtualKeyboard(); + ~VrVirtualKeyboard(); + + void onResChange(int w, int h) override; + + void onFrame(float dt) override; + + bool exit() override; + + void open(MyGUI::EditBox* target); + + void close(); + + void delegateOnSetFocus(MyGUI::Widget* _sender, MyGUI::Widget* _old); + void delegateOnLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _old); + + private: + void onButtonClicked(MyGUI::Widget* sender); + void textInput(const std::string& symbol); + void onEsc(); + void onTab(); + void onCaps(); + void onShift(); + void onBackspace(); + void onReturn(); + void updateMenu(); + + MyGUI::Widget* mButtonBox; + MyGUI::EditBox* mTarget; + std::map mButtons; + bool mShift; + bool mCaps; + }; + + class VirtualKeyboardManager : public Gui::VirtualKeyboardManager + { + public: + VirtualKeyboardManager(); + + void registerEditBox(MyGUI::EditBox* editBox) override; + void unregisterEditBox(MyGUI::EditBox* editBox) override; + VrVirtualKeyboard& virtualKeyboard() { return *mVk; }; + + private: + std::unique_ptr mVk; + + // MyGUI deletes delegates when you remove them from an event. + // Therefore i need one pair of delegates per box instead of being able to reuse one pair. + // And i have to set them aside myself to know what to remove from each event. + // There is an IDelegateUnlink type that might simplify this, but it is poorly documented. + using IDelegate = MyGUI::EventHandle_WidgetWidget::IDelegate; + // .first = onSetFocus, .second = onLostFocus + using Delegates = std::pair; + std::map mDelegates; + }; +} + +#endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 234947c32..1f8e2a831 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3966,7 +3966,7 @@ namespace MWWorld if (windowManager->isGuiMode() && windowManager->isConsoleMode()) { - return getTargetObject(result, pointer, getMaxActivationDistance() * 50, false); + return getTargetObject(result, pointer, getMaxActivationDistance() * 50, true); } else { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 69d8145f0..a221af51d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -131,7 +131,7 @@ add_component_dir (myguiplatform ) add_component_dir (widgets - box fontwrapper imagebutton tags list numericeditbox sharedstatebutton windowcaption widgets + box fontwrapper imagebutton tags list numericeditbox sharedstatebutton virtualkeyboardmanager windowcaption widgets ) add_component_dir (fontloader diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 1db193320..c38864b0e 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -1,4 +1,5 @@ #include "box.hpp" +#include "virtualkeyboardmanager.hpp" #include @@ -486,4 +487,36 @@ namespace Gui setUserString("VStretch", "true"); } + + EditBox::EditBox(bool shouldSupportVirtualKeyboard) + : mVirtualKeyboardRegistered(false) + { + if (shouldSupportVirtualKeyboard) + registerVirtualKeyboard(); + } + EditBox::~EditBox() + { + unregisterVirtualKeyboard(); + } + void EditBox::registerVirtualKeyboard() + { + if (!mVirtualKeyboardRegistered) + { + auto* vkm = Gui::VirtualKeyboardManager::getInstancePtr(); + if (vkm) + { + vkm->registerEditBox(this); + mVirtualKeyboardRegistered = true; + } + } + } + void EditBox::unregisterVirtualKeyboard() + { + if (mVirtualKeyboardRegistered) + { + // No need to check here + Gui::VirtualKeyboardManager::getInstance().unregisterEditBox(this); + mVirtualKeyboardRegistered = false; + } + } } diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index e84c3bb1f..54195a46f 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -23,7 +23,17 @@ namespace Gui class EditBox : public FontWrapper { - MYGUI_RTTI_DERIVED( EditBox ) + MYGUI_RTTI_DERIVED( EditBox ); + + /// @param supportsVirtualKeyboard If true, VR mode will spawn a virtual keyboard whenever this widget is focused. + EditBox(bool shouldSupportVirtualKeyboard = true); + ~EditBox(); + + private: + void registerVirtualKeyboard(); + void unregisterVirtualKeyboard(); + + bool mVirtualKeyboardRegistered; }; class AutoSizedWidget diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index ff16424d0..2d7632f07 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -3,7 +3,7 @@ #include -#include "fontwrapper.hpp" +#include "box.hpp" namespace Gui { @@ -11,7 +11,7 @@ namespace Gui /** * @brief A variant of the EditBox that only allows integer inputs */ - class NumericEditBox final : public FontWrapper + class NumericEditBox final : public Gui::EditBox { MYGUI_RTTI_DERIVED(NumericEditBox) diff --git a/components/widgets/virtualkeyboardmanager.cpp b/components/widgets/virtualkeyboardmanager.cpp new file mode 100644 index 000000000..24b578b4d --- /dev/null +++ b/components/widgets/virtualkeyboardmanager.cpp @@ -0,0 +1,4 @@ +#include "virtualkeyboardmanager.hpp" + +Gui::VirtualKeyboardManager* MyGUI::Singleton::msInstance = nullptr; +const char* MyGUI::Singleton::mClassTypeName = "Gui::VirtualKeyboardManager"; diff --git a/components/widgets/virtualkeyboardmanager.hpp b/components/widgets/virtualkeyboardmanager.hpp new file mode 100644 index 000000000..f8335294a --- /dev/null +++ b/components/widgets/virtualkeyboardmanager.hpp @@ -0,0 +1,18 @@ +#ifndef OPENMW_WIDGETS_VIRTUALKEYBOARDMANAGER_H +#define OPENMW_WIDGETS_VIRTUALKEYBOARDMANAGER_H + +#include +#include "MyGUI_Singleton.h" + +namespace Gui +{ + class VirtualKeyboardManager : + public MyGUI::Singleton + { + public: + virtual void registerEditBox(MyGUI::EditBox* editBox) = 0; + virtual void unregisterEditBox(MyGUI::EditBox* editBox) = 0; + }; +} + +#endif diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index e6ffb4306..ebc278840 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -98,6 +98,7 @@ set(MYGUI_FILES openmw_trade_window_vr.layout openmw_trainingwindow.layout openmw_travel_window.layout + openmw_vr_virtual_keyboard.layout openmw_wait_dialog.layout openmw_wait_dialog_progressbar.layout openmw_windows.skin.xml diff --git a/files/mygui/openmw_layers_vr.xml b/files/mygui/openmw_layers_vr.xml index fcb81f66d..dea1f59dc 100644 --- a/files/mygui/openmw_layers_vr.xml +++ b/files/mygui/openmw_layers_vr.xml @@ -27,5 +27,6 @@ + \ No newline at end of file diff --git a/files/mygui/openmw_vr_virtual_keyboard.layout b/files/mygui/openmw_vr_virtual_keyboard.layout new file mode 100644 index 000000000..059a2f67f --- /dev/null +++ b/files/mygui/openmw_vr_virtual_keyboard.layout @@ -0,0 +1,8 @@ + + + + + + + +