From 71fd8b8840e25d6cd3857beb9f24555fc854af0e Mon Sep 17 00:00:00 2001 From: Andrew Lanzone Date: Sun, 25 May 2025 23:14:13 -0700 Subject: [PATCH] Add controller support to spell buying and training windows --- apps/openmw/mwgui/spellbuyingwindow.cpp | 55 +++++++++++++++++++++++++ apps/openmw/mwgui/spellbuyingwindow.hpp | 3 ++ apps/openmw/mwgui/trainingwindow.cpp | 49 ++++++++++++++++++++++ apps/openmw/mwgui/trainingwindow.hpp | 4 ++ 4 files changed, 111 insertions(+) diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 9fca86caba..e1f12ae6e0 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -31,6 +31,13 @@ namespace MWGui getWidget(mSpellsView, "SpellsView"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked); + + if (Settings::gui().mControllerMenus) + { + mDisableGamepadCursor = true; + mControllerButtons.a = "#{sBuy}"; + mControllerButtons.b = "#{sBack}"; + } } bool SpellBuyingWindow::sortSpells(const ESM::Spell* left, const ESM::Spell* right) @@ -70,6 +77,8 @@ namespace MWGui toAdd->setUserString("SpellCost", std::to_string(spell.mData.mCost)); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onSpellButtonClick); mSpellsWidgetMap.insert(std::make_pair(toAdd, spell.mId)); + if (price <= playerGold) + mSpellButtons.emplace_back(toAdd); } void SpellBuyingWindow::clearSpells() @@ -79,6 +88,7 @@ namespace MWGui while (mSpellsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mSpellsView->getChildAt(0)); mSpellsWidgetMap.clear(); + mSpellButtons.clear(); } void SpellBuyingWindow::setPtr(const MWWorld::Ptr& actor) @@ -129,6 +139,13 @@ namespace MWGui updateLabels(); + if (Settings::gui().mControllerMenus) + { + mControllerFocus = 0; + if (mSpellButtons.size() > 0) + mSpellButtons[0]->setStateSelected(true); + } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the // scrollbar is hidden mSpellsView->setVisibleVScroll(false); @@ -199,4 +216,42 @@ namespace MWGui mSpellsView->setViewOffset( MyGUI::IntPoint(0, static_cast(mSpellsView->getViewOffset().top + _rel * 0.3f))); } + + bool SpellBuyingWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + onSpellButtonClick(mSpellButtons[mControllerFocus]); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onCancelButtonClicked(mCancelButton); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP) + { + if (mSpellButtons.size() <= 1) + return true; + + mSpellButtons[mControllerFocus]->setStateSelected(false); + mControllerFocus = wrap(mControllerFocus - 1, mSpellButtons.size()); + mSpellButtons[mControllerFocus]->setStateSelected(true); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) + { + if (mSpellButtons.size() <= 1) + return true; + + mSpellButtons[mControllerFocus]->setStateSelected(false); + mControllerFocus = wrap(mControllerFocus + 1, mSpellButtons.size()); + mSpellButtons[mControllerFocus]->setStateSelected(true); + } + + // Scroll the list to keep the active item in view + if (mControllerFocus <= 5) + mSpellsView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mSpellsView->setViewOffset(MyGUI::IntPoint(0, -10 * (mControllerFocus - 5))); + + return true; + } } diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index 257b8a0df9..234dcb5b9d 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -39,6 +39,7 @@ namespace MWGui MyGUI::ScrollView* mSpellsView; std::map mSpellsWidgetMap; + std::vector mSpellButtons; void onCancelButtonClicked(MyGUI::Widget* _sender); void onSpellButtonClick(MyGUI::Widget* _sender); @@ -55,6 +56,8 @@ namespace MWGui private: static bool sortSpells(const ESM::Spell* left, const ESM::Spell* right); + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; + int mControllerFocus; }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 890aa0ba68..371f0e95aa 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -37,6 +37,13 @@ namespace MWGui mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &TrainingWindow::onTrainingProgressChanged); mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &TrainingWindow::onTrainingFinished); + + if (Settings::gui().mControllerMenus) + { + mDisableGamepadCursor = true; + mControllerButtons.a = "#{sBuy}"; + mControllerButtons.b = "#{sBack}"; + } } void TrainingWindow::onOpen() @@ -105,6 +112,7 @@ namespace MWGui const int lineHeight = Settings::gui().mFontSize + 2; + mTrainingButtons.clear(); for (size_t i = 0; i < skills.size(); ++i) { const ESM::Skill* skill = skills[i].first; @@ -128,6 +136,16 @@ namespace MWGui button->setSize(button->getTextSize().width + 12, button->getSize().height); ToolTips::createSkillToolTip(button, skill->mId); + + if (price <= playerGold) + mTrainingButtons.emplace_back(button); + } + + if (Settings::gui().mControllerMenus) + { + mControllerFocus = 0; + if (mTrainingButtons.size() > 0) + mTrainingButtons[0]->setStateSelected(true); } center(); @@ -229,4 +247,35 @@ namespace MWGui return !mTimeAdvancer.isRunning(); } + bool TrainingWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + onTrainingSelected(mTrainingButtons[mControllerFocus]); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + onCancelButtonClicked(mCancelButton); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP) + { + if (mTrainingButtons.size() <= 1) + return true; + + mTrainingButtons[mControllerFocus]->setStateSelected(false); + mControllerFocus = wrap(mControllerFocus - 1, mTrainingButtons.size()); + mTrainingButtons[mControllerFocus]->setStateSelected(true); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) + { + if (mTrainingButtons.size() <= 1) + return true; + + mTrainingButtons[mControllerFocus]->setStateSelected(false); + mControllerFocus = wrap(mControllerFocus + 1, mTrainingButtons.size()); + mTrainingButtons[mControllerFocus]->setStateSelected(true); + } + + return true; + } } diff --git a/apps/openmw/mwgui/trainingwindow.hpp b/apps/openmw/mwgui/trainingwindow.hpp index ee13f24b23..4f866f820e 100644 --- a/apps/openmw/mwgui/trainingwindow.hpp +++ b/apps/openmw/mwgui/trainingwindow.hpp @@ -49,9 +49,13 @@ namespace MWGui MyGUI::Widget* mTrainingOptions; MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; + std::vector mTrainingButtons; WaitDialogProgressBar mProgressBar; TimeAdvancer mTimeAdvancer; + + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; + int mControllerFocus; }; }