From 83162477ec50199a06c11336fa378736c0efe043 Mon Sep 17 00:00:00 2001 From: Andrew Lanzone Date: Mon, 12 May 2025 01:34:28 -0700 Subject: [PATCH] Basic controller support for all character creation windows --- apps/openmw/mwgui/birth.cpp | 31 ++++ apps/openmw/mwgui/birth.hpp | 2 + apps/openmw/mwgui/class.cpp | 192 ++++++++++++++++++++++- apps/openmw/mwgui/class.hpp | 13 ++ apps/openmw/mwgui/confirmationdialog.hpp | 2 +- apps/openmw/mwgui/race.cpp | 8 + apps/openmw/mwgui/race.hpp | 3 +- apps/openmw/mwgui/review.cpp | 76 +++++++++ apps/openmw/mwgui/review.hpp | 5 + 9 files changed, 322 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 3dfdd17627..bf495273be 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -271,4 +271,35 @@ namespace MWGui mSpellArea->setVisibleVScroll(true); mSpellArea->setViewOffset(MyGUI::IntPoint(0, 0)); } + + bool BirthDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + // Have A button do nothing so mouse controller still works. + return false; + } + else if (arg.button == SDL_CONTROLLER_BUTTON_START) + { + onOkClicked(nullptr); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onBackClicked(nullptr); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP) + { + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + winMgr->setKeyFocusWidget(mBirthList); + winMgr->injectKeyPress(MyGUI::KeyCode::ArrowUp, 0, false); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) + { + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + winMgr->setKeyFocusWidget(mBirthList); + winMgr->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false); + } + + return true; + } } diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index db9e997b6c..20db2b70de 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -55,6 +55,8 @@ namespace MWGui std::vector mSpellItems; ESM::RefId mCurrentBirthId; + + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; }; } #endif diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 839f0f5072..25218c9e25 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "tooltips.hpp" @@ -46,15 +47,20 @@ namespace MWGui getWidget(mClassImage, "ClassImage"); getWidget(mClassName, "ClassName"); - MyGUI::Button* backButton; - getWidget(backButton, "BackButton"); - backButton->setCaptionWithReplacing("#{sMessageQuestionAnswer3}"); - backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked); + getWidget(mBackButton, "BackButton"); + mBackButton->setCaptionWithReplacing("#{sMessageQuestionAnswer3}"); + mBackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked); - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); - okButton->setCaptionWithReplacing("#{sMessageQuestionAnswer2}"); - okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); + getWidget(mOkButton, "OKButton"); + mOkButton->setCaptionWithReplacing("#{sMessageQuestionAnswer2}"); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); + + if (Settings::gui().mControllerMenus) + { + mOkButton->setStateSelected(true); + trackFocusEvents(mBackButton); + trackFocusEvents(mOkButton); + } center(); } @@ -71,6 +77,33 @@ namespace MWGui center(); } + bool GenerateClassResultDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + if (mMouseFocus != nullptr) + return false; + + if (mOkButtonFocus) + onOkClicked(mOkButton); + else + onBackClicked(mBackButton); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onBackClicked(mBackButton); + } + else if ((arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && mOkButtonFocus) || + (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && !mOkButtonFocus)) + { + mOkButtonFocus = !mOkButtonFocus; + mOkButton->setStateSelected(mOkButtonFocus); + mBackButton->setStateSelected(!mOkButtonFocus); + } + + return true; + } + // widget controls void GenerateClassResultDialog::onOkClicked(MyGUI::Widget* _sender) @@ -278,6 +311,37 @@ namespace MWGui setClassImage(mClassImage, mCurrentClassId); } + bool PickClassDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + // Have A button do nothing so mouse controller still works. + return false; + } + else if (arg.button == SDL_CONTROLLER_BUTTON_START) + { + onOkClicked(nullptr); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onBackClicked(nullptr); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP) + { + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + winMgr->setKeyFocusWidget(mClassList); + winMgr->injectKeyPress(MyGUI::KeyCode::ArrowUp, 0, false); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) + { + MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager(); + winMgr->setKeyFocusWidget(mClassList); + winMgr->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false); + } + + return true; + } + /* InfoBoxDialog */ void InfoBoxDialog::fitToText(MyGUI::TextBox* widget) @@ -353,6 +417,14 @@ namespace MWGui fitToText(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &InfoBoxDialog::onButtonClicked); coord.top += button->getHeight(); + + if (Settings::gui().mControllerMenus && buttons.size() > 1 && this->mButtons.empty()) + { + // First button is selected by default + button->setStateSelected(true); + } + trackFocusEvents(button); + this->mButtons.push_back(button); } } @@ -382,6 +454,53 @@ namespace MWGui } } + bool InfoBoxDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + if (mMouseFocus != nullptr) + return false; + + onButtonClicked(mButtons[mControllerFocus]); + return true; + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + if (mButtons.size() == 1) + onButtonClicked(mButtons[0]); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP) + { + if (mButtons.size() <= 1) + return true; + if (mButtons.size() == 2 && mControllerFocus == 0) + return true; + + mButtons[mControllerFocus]->setStateSelected(false); + if (mControllerFocus == 0) + mControllerFocus = mButtons.size() - 1; + else + mControllerFocus--; + mButtons[mControllerFocus]->setStateSelected(true); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) + { + if (mButtons.size() <= 1) + return true; + if (mButtons.size() == 2 && mControllerFocus == mButtons.size() - 1) + return true; + + mButtons[mControllerFocus]->setStateSelected(false); + if (mControllerFocus == mButtons.size() - 1) + mControllerFocus = 0; + else + mControllerFocus++; + mButtons[mControllerFocus]->setStateSelected(true); + } + + return true; + } + /* ClassChoiceDialog */ ClassChoiceDialog::ClassChoiceDialog() @@ -552,6 +671,24 @@ namespace MWGui MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } + bool CreateClassDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + // Have A button do nothing so mouse controller still works. + return false; + } + else if (arg.button == SDL_CONTROLLER_BUTTON_START) + { + onOkClicked(nullptr); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onBackClicked(nullptr); + } + return true; + } + // widget controls void CreateClassDialog::onDialogCancel() @@ -739,6 +876,16 @@ namespace MWGui return true; } + bool SelectSpecializationDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onCancelClicked(nullptr); + return true; + } + return false; + } + /* SelectAttributeDialog */ SelectAttributeDialog::SelectAttributeDialog() @@ -791,6 +938,16 @@ namespace MWGui return true; } + bool SelectAttributeDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onCancelClicked(nullptr); + return true; + } + return false; + } + /* SelectSkillDialog */ SelectSkillDialog::SelectSkillDialog() @@ -855,6 +1012,16 @@ namespace MWGui return true; } + bool SelectSkillDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onCancelClicked(nullptr); + return true; + } + return false; + } + /* DescriptionDialog */ DescriptionDialog::DescriptionDialog() @@ -904,4 +1071,13 @@ namespace MWGui imageBox->setImageTexture(classImage); } + bool DescriptionDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A || arg.button == SDL_CONTROLLER_BUTTON_B) + { + onOkClicked(nullptr); + return true; + } + return false; + } } diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index f89a0c7d88..4567776beb 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -42,6 +42,7 @@ namespace MWGui protected: void onButtonClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: void fitToText(MyGUI::TextBox* widget); @@ -50,6 +51,7 @@ namespace MWGui MyGUI::TextBox* mText; MyGUI::Widget* mButtonBar; std::vector mButtons; + int mControllerFocus = 0; }; // Lets the player choose between 3 ways of creating a class @@ -92,10 +94,14 @@ namespace MWGui protected: void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; + bool mOkButtonFocus = true; private: MyGUI::ImageBox* mClassImage; MyGUI::TextBox* mClassName; + MyGUI::Button* mBackButton; + MyGUI::Button* mOkButton; ESM::RefId mCurrentClassId; }; @@ -132,6 +138,7 @@ namespace MWGui void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: void updateClasses(); @@ -173,6 +180,7 @@ namespace MWGui protected: void onSpecializationClicked(MyGUI::Widget* _sender); void onCancelClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: MyGUI::TextBox *mSpecialization0, *mSpecialization1, *mSpecialization2; @@ -206,6 +214,7 @@ namespace MWGui protected: void onAttributeClicked(Widgets::MWAttributePtr _sender); void onCancelClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: ESM::RefId mAttributeId; @@ -237,6 +246,7 @@ namespace MWGui protected: void onSkillClicked(Widgets::MWSkillPtr _sender); void onCancelClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: ESM::RefId mSkillId; @@ -258,6 +268,7 @@ namespace MWGui protected: void onOkClicked(MyGUI::Widget* _sender); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: MyGUI::EditBox* mTextEdit; @@ -329,6 +340,8 @@ namespace MWGui Widgets::MWAttributePtr mAffectedAttribute; Widgets::MWSkillPtr mAffectedSkill; + + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; }; } #endif diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index 93d7f360c5..9b26e3a3c9 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -29,7 +29,7 @@ namespace MWGui void onOkButtonClicked(MyGUI::Widget* _sender); bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; - int mOkButtonFocus = true; + bool mOkButtonFocus = true; }; } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 6d7ec19f88..6a62fb3b47 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -501,6 +501,14 @@ namespace MWGui winMgr->setKeyFocusWidget(mRaceList); winMgr->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false); } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) + { + onPreviewScroll(nullptr, 5); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) + { + onPreviewScroll(nullptr, -5); + } return true; } diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 5452753747..8589743b9e 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -3,6 +3,7 @@ #include "windowbase.hpp" #include +#include #include namespace MWRender @@ -100,7 +101,7 @@ namespace MWGui MyGUI::ImageBox* mPreviewImage; MyGUI::ListBox* mRaceList; - MyGUI::ScrollBar* mHeadRotate; + Gui::ScrollBar* mHeadRotate; MyGUI::Widget* mSkillList; std::vector mSkillItems; diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index ddce2c5f50..b113ace2be 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -46,21 +46,29 @@ namespace MWGui getWidget(button, "NameButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked); + trackFocusEvents(button); + mButtons.push_back(button); getWidget(mRaceWidget, "RaceText"); getWidget(button, "RaceButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked); + trackFocusEvents(button); + mButtons.push_back(button); getWidget(mClassWidget, "ClassText"); getWidget(button, "ClassButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked); + trackFocusEvents(button); + mButtons.push_back(button); getWidget(mBirthSignWidget, "SignText"); getWidget(button, "SignButton"); adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked); + trackFocusEvents(button); + mButtons.push_back(button); // Setup dynamic stats getWidget(mHealth, "Health"); @@ -108,10 +116,17 @@ namespace MWGui MyGUI::Button* backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked); + trackFocusEvents(backButton); + mButtons.push_back(backButton); MyGUI::Button* okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked); + trackFocusEvents(okButton); + mButtons.push_back(okButton); + + if (Settings::gui().mControllerMenus) + mButtons[mControllerFocus]->setStateSelected(true); } void ReviewDialog::onOpen() @@ -522,4 +537,65 @@ namespace MWGui MyGUI::IntPoint(0, static_cast(mSkillView->getViewOffset().top + _rel * 0.3))); } + bool ReviewDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + if (mMouseFocus != nullptr) + return false; + + switch(mControllerFocus) + { + case 0: + onNameClicked(mButtons[0]); + break; + case 1: + onRaceClicked(mButtons[1]); + break; + case 2: + onClassClicked(mButtons[2]); + break; + case 3: + onBirthSignClicked(mButtons[3]); + break; + case 4: + onBackClicked(mButtons[4]); + break; + case 5: + onOkClicked(mButtons[5]); + break; + } + return true; + } + else if (arg.button == SDL_CONTROLLER_BUTTON_START) + { + onOkClicked(mButtons[5]); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onBackClicked(mButtons[4]); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP || + arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) + { + mButtons[mControllerFocus]->setStateSelected(false); + if (mControllerFocus == 0) + mControllerFocus = mButtons.size() - 1; + else + mControllerFocus--; + mButtons[mControllerFocus]->setStateSelected(true); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN || + arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) + { + mButtons[mControllerFocus]->setStateSelected(false); + if (mControllerFocus == mButtons.size() - 1) + mControllerFocus = 0; + else + mControllerFocus++; + mButtons[mControllerFocus]->setStateSelected(true); + } + + return true; + } } diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 7226ad628d..fe53787fe3 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -72,6 +72,7 @@ namespace MWGui void onBirthSignClicked(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; private: void addSkills(const std::vector& skills, const std::string& titleId, @@ -100,6 +101,10 @@ namespace MWGui std::vector mSkillWidgets; //< Skills and other information bool mUpdateSkillArea; + + // 0 = Name, 1 = Race, 2 = Class, 3 = BirthSign, 4 = Back, 5 = OK + std::vector mButtons; + int mControllerFocus = 5; }; } #endif