From a8824b46a8f2f871c280fc32897a767361dd24bf Mon Sep 17 00:00:00 2001 From: Andrew Lanzone Date: Fri, 9 May 2025 22:56:04 -0700 Subject: [PATCH] Add first batch of controller-enabled windows --- apps/openmw/mwbase/windowmanager.hpp | 3 ++ apps/openmw/mwgui/confirmationdialog.cpp | 17 ++++++++++++ apps/openmw/mwgui/confirmationdialog.hpp | 2 ++ apps/openmw/mwgui/mainmenu.cpp | 31 +++++++++++++++++++-- apps/openmw/mwgui/mainmenu.hpp | 1 + apps/openmw/mwgui/waitdialog.cpp | 25 +++++++++++++++++ apps/openmw/mwgui/waitdialog.hpp | 1 + apps/openmw/mwgui/windowbase.cpp | 25 +++++++++++++++++ apps/openmw/mwgui/windowbase.hpp | 15 ++++++++++ apps/openmw/mwgui/windowmanagerimp.cpp | 34 +++++++++++++++++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 2 ++ apps/openmw/mwinput/controllermanager.cpp | 12 ++++++++ 12 files changed, 166 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 8164501b4b..d2f1fc6514 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -12,6 +12,7 @@ #include #include "../mwgui/mode.hpp" +#include "../mwgui/windowbase.hpp" #include @@ -381,6 +382,8 @@ namespace MWBase /// Same as viewer->getCamera()->getCullMask(), provided for consistency. virtual uint32_t getCullMask() = 0; + virtual MWGui::WindowBase* getTopWindow() = 0; + // Used in Lua bindings virtual const std::vector& getGuiModeStack() const = 0; virtual void setDisabledByLua(std::string_view windowId, bool disabled) = 0; diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 48b209f17e..903fb4f898 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -17,6 +17,8 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onCancelButtonClicked); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked); + + trackFocusEvents(mCancelButton); } void ConfirmationDialog::askForConfirmation(const std::string& message) @@ -56,4 +58,19 @@ namespace MWGui eventOkClicked(); } + + bool ConfirmationDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + if (mMouseFocus != nullptr) + return false; + + onOkButtonClicked(mOkButton); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + onCancelButtonClicked(mCancelButton); + + return true; + } } diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index 1344f2a501..2a1886398b 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -27,6 +27,8 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* _sender); void onOkButtonClicked(MyGUI::Widget* _sender); + + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; }; } diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 1b3619bd9f..ae5b5f9be2 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,6 +1,7 @@ #include "mainmenu.hpp" #include +#include #include #include @@ -163,9 +164,7 @@ namespace MWGui const std::string& name = *sender->getUserData(); winMgr->playSound(ESM::RefId::stringRefId("Menu Click")); if (name == "return") - { winMgr->removeGuiMode(GM_MainMenu); - } else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); else if (name == "exitgame") @@ -208,6 +207,34 @@ namespace MWGui } } + bool MainMenu::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + // REMOVEME + Log(Debug::Verbose) << "MainMenu::onControllerButtonEvent " << arg.button; + + MyGUI::KeyCode key = MyGUI::KeyCode::None; + switch (arg.button) + { + case SDL_CONTROLLER_BUTTON_DPAD_UP: + MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::LeftShift); + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Tab, 0, false); + MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::LeftShift); + return true; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + key = MyGUI::KeyCode::Tab; + break; + case SDL_CONTROLLER_BUTTON_A: + key = MyGUI::KeyCode::Space; + break; + case SDL_CONTROLLER_BUTTON_B: + case SDL_CONTROLLER_BUTTON_START: + onButtonClicked(mButtons["return"]); + return true; + } + MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false); + return true; + } + void MainMenu::showBackground(bool show) { if (mVideo && !show) diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 06a8c945c1..453a16b5e4 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -49,6 +49,7 @@ namespace MWGui MainMenu(int w, int h, const VFS::Manager* vfs, const std::string& versionDescription); void onResChange(int w, int h) override; + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; void setVisible(bool visible) override; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 568f05abc3..3c39590dee 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -80,6 +80,10 @@ namespace MWGui mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &WaitDialog::onWaitingProgressChanged); mTimeAdvancer.eventInterrupted += MyGUI::newDelegate(this, &WaitDialog::onWaitingInterrupted); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &WaitDialog::onWaitingFinished); + + trackFocusEvents(mUntilHealedButton); + trackFocusEvents(mWaitButton); + trackFocusEvents(mCancelButton); } void WaitDialog::setPtr(const MWWorld::Ptr& ptr) @@ -326,6 +330,27 @@ namespace MWGui } } + bool WaitDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + if (mMouseFocus != nullptr) + return false; + + onWaitButtonClicked(mWaitButton); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + onCancelButtonClicked(mCancelButton); + else if (arg.button == SDL_CONTROLLER_BUTTON_X && mUntilHealedButton->getVisible()) + onUntilHealedButtonClicked(mUntilHealedButton); + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false); + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::ArrowUp, 0, false); + + return true; + } + void WaitDialog::stopWaiting() { MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f); diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index 3d66584f54..8a38dd0976 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -36,6 +36,7 @@ namespace MWGui void clear() override; void onFrame(float dt) override; + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; bool getSleeping() { return mTimeAdvancer.isRunning() && mSleeping; } void wakeUp(); diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index f5d90590f8..d01822f704 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -7,6 +7,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include #include #include "draganddrop.hpp" @@ -105,6 +106,30 @@ void WindowBase::clampWindowCoordinates(MyGUI::Window* window) window->setPosition(left, top); } +void WindowBase::focusGain(MyGUI::Widget* _new, MyGUI::Widget* _old) +{ + // REMOVEME + Log(Debug::Verbose) << "WindowBase::focusGain new=" << _new << ", old=" << _old; + mMouseFocus = _new; +} + +void WindowBase::focusLoss(MyGUI::Widget* _old, MyGUI::Widget* _new) +{ + // REMOVEME + Log(Debug::Verbose) << "WindowBase::focusLoss old=" << _old << ", new=" << _new; + if (mMouseFocus == _old) + mMouseFocus = nullptr; +} + +void WindowBase::trackFocusEvents(MyGUI::Widget* widget) +{ + if (!Settings::gui().mControllerMenus) + return; + + widget->eventMouseSetFocus += MyGUI::newDelegate(this, &WindowBase::focusGain); + widget->eventMouseLostFocus += MyGUI::newDelegate(this, &WindowBase::focusLoss); +} + WindowModal::WindowModal(const std::string& parLayout) : WindowBase(parLayout) { diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 466060c6ad..3db4399d85 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_WINDOW_BASE_H #define MWGUI_WINDOW_BASE_H +#include + #include "layout.hpp" namespace MWWorld @@ -54,13 +56,26 @@ namespace MWGui static void clampWindowCoordinates(MyGUI::Window* window); + /// Called by controllermanager to handle controller events + virtual bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) { return true; }; + virtual bool onControllerThumbstickEvent(const SDL_ControllerAxisEvent& arg) { return true; }; + // REMOVEME + // virtual bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) = 0; + // virtual bool onControllerThumbstickEvent(const SDL_ControllerAxisEvent& arg) = 0; + protected: virtual void onTitleDoubleClicked(); + MyGUI::Widget* mMouseFocus = nullptr; + void trackFocusEvents(MyGUI::Widget* widget); + private: void onDoubleClick(MyGUI::Widget* _sender); bool mDisabledByLua = false; + + void focusGain(MyGUI::Widget* _new, MyGUI::Widget* _old); + void focusLoss(MyGUI::Widget* _old, MyGUI::Widget* _new); }; /* diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 565fb43127..b328176014 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -859,6 +859,40 @@ namespace MWGui mHud->setPlayerPos(x, y, u, v); } + WindowBase* WindowManager::getTopWindow() + { + if (!mCurrentModals.empty()) + return mCurrentModals.back(); + + if (isSettingsWindowVisible()) + return mSettingsWindow; + + if (!mGuiModes.empty()) + { + GuiModeState& state = mGuiModeStates[mGuiModes.back()]; + // REMOVEME + Log(Debug::Error) << "getTopWindow: " << state.mWindows.size() << " windows in state " << mGuiModes.back(); + // find the topmost window + for (WindowBase* window : state.mWindows) + if (window->isVisible()) + return window; + else + Log(Debug::Error) << "-- Skipping hidden window " << window; + } + else + { + // return pinned windows if visible + // REMOVEME + Log(Debug::Error) << "getTopWindow: " << mGuiModeStates[GM_Inventory].mWindows.size() << " pinned windows"; + for (WindowBase* window : mGuiModeStates[GM_Inventory].mWindows) + if (window->isVisible()) + return window; + else + Log(Debug::Error) << "-- Skipping hidden window " << window; + } + return nullptr; + } + void WindowManager::update(float frameDuration) { handleScheduledMessageBoxes(); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 03902e21c4..fd035ed12e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -387,6 +387,8 @@ namespace MWGui void asyncPrepareSaveMap() override; + WindowBase* getTopWindow() override; + // Used in Lua bindings const std::vector& getGuiModeStack() const override { return mGuiModes; } void setDisabledByLua(std::string_view windowId, bool disabled) override; diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 1a8490d8b7..404b156c24 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -241,6 +241,12 @@ namespace MWInput bool ControllerManager::gamepadToGuiControl(const SDL_ControllerButtonEvent& arg) { + if (Settings::gui().mControllerMenus) + { + MWGui::WindowBase* topWin = MWBase::Environment::get().getWindowManager()->getTopWindow(); + return topWin && topWin->onControllerButtonEvent(arg); + } + // Presumption of GUI mode will be removed in the future. // MyGUI KeyCodes *may* change. MyGUI::KeyCode key = MyGUI::KeyCode::None; @@ -302,6 +308,12 @@ namespace MWInput bool ControllerManager::gamepadToGuiControl(const SDL_ControllerAxisEvent& arg) { + if (Settings::gui().mControllerMenus) + { + MWGui::WindowBase* topWin = MWBase::Environment::get().getWindowManager()->getTopWindow(); + return topWin && topWin->onControllerThumbstickEvent(arg); + } + switch (arg.axis) { case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: