diff --git a/CMakeLists.txt b/CMakeLists.txt
index c0d09542f7..201cc600c7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 50)
set(OPENMW_VERSION_RELEASE 0)
-set(OPENMW_LUA_API_REVISION 89)
+set(OPENMW_LUA_API_REVISION 90)
set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "")
diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp
index 849f13076d..39396f1273 100644
--- a/apps/launcher/settingspage.cpp
+++ b/apps/launcher/settingspage.cpp
@@ -303,6 +303,9 @@ bool Launcher::SettingsPage::loadSettings()
loadSettingBool(Settings::gui().mColorTopicEnable, *changeDialogTopicsCheckBox);
showOwnedComboBox->setCurrentIndex(Settings::game().mShowOwned);
loadSettingBool(Settings::gui().mStretchMenuBackground, *stretchBackgroundCheckBox);
+ connect(controllerMenusCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotControllerMenusToggled);
+ loadSettingBool(Settings::gui().mControllerMenus, *controllerMenusCheckBox);
+ loadSettingBool(Settings::gui().mControllerTooltips, *controllerMenuTooltipsCheckBox);
loadSettingBool(Settings::map().mAllowZooming, *useZoomOnMapCheckBox);
loadSettingBool(Settings::game().mGraphicHerbalism, *graphicHerbalismCheckBox);
scalingSpinBox->setValue(Settings::gui().mScalingFactor);
@@ -496,6 +499,8 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*changeDialogTopicsCheckBox, Settings::gui().mColorTopicEnable);
saveSettingInt(*showOwnedComboBox, Settings::game().mShowOwned);
saveSettingBool(*stretchBackgroundCheckBox, Settings::gui().mStretchMenuBackground);
+ saveSettingBool(*controllerMenusCheckBox, Settings::gui().mControllerMenus);
+ saveSettingBool(*controllerMenuTooltipsCheckBox, Settings::gui().mControllerTooltips);
saveSettingBool(*useZoomOnMapCheckBox, Settings::map().mAllowZooming);
saveSettingBool(*graphicHerbalismCheckBox, Settings::game().mGraphicHerbalism);
Settings::gui().mScalingFactor.set(scalingSpinBox->value());
@@ -554,6 +559,11 @@ void Launcher::SettingsPage::slotAnimSourcesToggled(bool checked)
}
}
+void Launcher::SettingsPage::slotControllerMenusToggled(bool checked)
+{
+ controllerMenuTooltipsCheckBox->setEnabled(checked);
+}
+
void Launcher::SettingsPage::slotPostProcessToggled(bool checked)
{
postprocessTransparentPostpassCheckBox->setEnabled(checked);
diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp
index d2bb80d86a..2c6eca477a 100644
--- a/apps/launcher/settingspage.hpp
+++ b/apps/launcher/settingspage.hpp
@@ -34,6 +34,7 @@ namespace Launcher
void slotSkyBlendingToggled(bool checked);
void slotShadowDistLimitToggled(bool checked);
void slotDistantLandToggled(bool checked);
+ void slotControllerMenusToggled(bool checked);
private:
Config::GameSettings& mGameSettings;
diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui
index d7e1a4b3ab..a591c196c9 100644
--- a/apps/launcher/ui/settingspage.ui
+++ b/apps/launcher/ui/settingspage.ui
@@ -1433,6 +1433,29 @@
+ -
+
+
+ <html><head/><body><p>Make it easier to use game menus with a controller.</p></body></html>
+
+
+ Enable Controller Menus
+
+
+
+ -
+
+
+ false
+
+
+ <html><head/><body><p>When using controller menus, make tooltips visible by default.</p></body></html>
+
+
+ Show Controller Tooltips By Default
+
+
+
-
diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt
index 2a5ad2d18d..df6bf27eeb 100644
--- a/apps/openmw/CMakeLists.txt
+++ b/apps/openmw/CMakeLists.txt
@@ -44,7 +44,7 @@ add_openmw_dir (mwgui
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation textcolours statswatcher
- postprocessorhud settings worlditemmodel itemtransfer
+ postprocessorhud settings worlditemmodel itemtransfer controllerbuttonsoverlay inventorytabsoverlay
)
add_openmw_dir (mwdialogue
diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp
index de6cf91f4e..2861ab88e9 100644
--- a/apps/openmw/mwbase/inputmanager.hpp
+++ b/apps/openmw/mwbase/inputmanager.hpp
@@ -19,6 +19,11 @@ namespace ESM
class ESMWriter;
}
+namespace MyGUI
+{
+ class Widget;
+}
+
namespace MWBase
{
/// \brief Interface for input manager (implemented in MWInput)
@@ -61,6 +66,7 @@ namespace MWBase
virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1]
virtual int getMouseMoveX() const = 0;
virtual int getMouseMoveY() const = 0;
+ virtual void warpMouseToWidget(MyGUI::Widget* widget) = 0;
/// Actions available for binding to keyboard buttons
virtual const std::initializer_list& getActionKeySorting() = 0;
@@ -77,6 +83,8 @@ namespace MWBase
/// @return true if joystick, false otherwise
virtual bool joystickLastUsed() = 0;
virtual void setJoystickLastUsed(bool enabled) = 0;
+ virtual std::string getControllerButtonIcon(int button) = 0;
+ virtual std::string getControllerAxisIcon(int axis) = 0;
virtual int countSavedGameRecords() const = 0;
virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) = 0;
diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp
index 037f719e6d..2783fd21d5 100644
--- a/apps/openmw/mwbase/windowmanager.hpp
+++ b/apps/openmw/mwbase/windowmanager.hpp
@@ -78,6 +78,8 @@ namespace MWGui
class MessageBox;
class PostProcessorHud;
class SettingsWindow;
+ class HUD;
+ class WindowBase;
enum ShowInDialogueMode
{
@@ -157,7 +159,9 @@ namespace MWBase
virtual MWGui::CountDialog* getCountDialog() = 0;
virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0;
virtual MWGui::TradeWindow* getTradeWindow() = 0;
+ virtual MWGui::HUD* getHud() = 0;
virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0;
+ virtual std::vector getGuiModeWindows(MWGui::GuiMode mode) = 0;
/// Make the player use an item, while updating GUI state accordingly
virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0;
@@ -381,6 +385,17 @@ namespace MWBase
/// Same as viewer->getCamera()->getCullMask(), provided for consistency.
virtual uint32_t getCullMask() = 0;
+ /// Return the window that should receive controller events
+ virtual MWGui::WindowBase* getActiveControllerWindow() = 0;
+ /// Return the available height for menus accounting for visible controller overlays
+ virtual int getControllerMenuHeight() = 0;
+ /// Cycle to the next window to receive controller events
+ virtual void cycleActiveControllerWindow(bool next) = 0;
+ virtual void setActiveControllerWindow(MWGui::GuiMode mode, int activeIndex) = 0;
+ virtual bool getControllerTooltip() const = 0;
+ virtual void setControllerTooltip(bool enabled) = 0;
+ virtual void updateControllerButtonsOverlay() = 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/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp
index 5a6245fca0..66461e6e6a 100644
--- a/apps/openmw/mwgui/alchemywindow.cpp
+++ b/apps/openmw/mwgui/alchemywindow.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
@@ -91,6 +92,15 @@ namespace MWGui
mFilterValue->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited);
mFilterType->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::switchFilterType);
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mX = "#{sCreate}";
+ mControllerButtons.mY = "#{sMagicEffects}";
+ mControllerButtons.mR3 = "#{sInfo}";
+ }
+
center();
}
@@ -165,7 +175,12 @@ namespace MWGui
std::string_view ingredient = wm->getGameSettingString("sIngredients", "Ingredients");
if (mFilterType->getCaption() == ingredient)
- mCurrentFilter = FilterType::ByName;
+ {
+ if (Settings::gui().mControllerMenus)
+ switchFilterType(mFilterType);
+ else
+ mCurrentFilter = FilterType::ByName;
+ }
else
mCurrentFilter = FilterType::ByEffect;
updateFilters();
@@ -291,6 +306,9 @@ namespace MWGui
initFilter();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
+
+ if (Settings::gui().mControllerMenus)
+ mItemView->setActiveControllerWindow(true);
}
void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender)
@@ -528,4 +546,86 @@ namespace MWGui
if (currentCount > 1)
mBrewCountEdit->setValue(currentCount - 1);
}
+
+ void AlchemyWindow::filterListButtonHandler(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A || arg.button == SDL_CONTROLLER_BUTTON_Y)
+ {
+ // Select the highlighted entry in the combo box and close it. List is closed by focusing on another
+ // widget.
+ size_t index = mFilterValue->getIndexSelected();
+ mFilterValue->setIndexSelected(index);
+ onFilterChanged(mFilterValue, index);
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
+
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ // Close the list without selecting anything. List is closed by focusing on another widget.
+ mFilterValue->clearIndexSelected();
+ onFilterEdited(mFilterValue);
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::ArrowUp, 0, false);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false);
+ }
+
+ bool AlchemyWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
+ bool isFilterListOpen
+ = focus != nullptr && focus->getParent() != nullptr && focus->getParent()->getParent() == mFilterValue;
+
+ if (isFilterListOpen)
+ {
+ // When the filter list combo box is open, send all inputs to it.
+ filterListButtonHandler(arg);
+ return true;
+ }
+
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ // Remove active ingredients or close the window, starting with right-most slot.
+ for (int i = mIngredients.size() - 1; i >= 0; --i)
+ {
+ if (mIngredients[i]->isUserString("ToolTipType"))
+ {
+ onIngredientSelected(mIngredients[i]);
+ return true;
+ }
+ }
+ // If the ingredients list is empty, B closes the menu.
+ onCancelButtonClicked(mCancelButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ onCreateButtonClicked(mCreateButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y && mFilterValue->getItemCount() > 0)
+ {
+ // Magical effects/ingredients filter
+ if (mFilterValue->getIndexSelected() != MyGUI::ITEM_NONE)
+ {
+ // Clear the active filter
+ mFilterValue->clearIndexSelected();
+ onFilterEdited(mFilterValue);
+ }
+ else
+ {
+ // Open the combo box to choose the a filter
+ MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mFilterValue);
+ MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false);
+ }
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ onDecreaseButtonTriggered();
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ onIncreaseButtonTriggered();
+ else
+ mItemView->onControllerButton(arg.button);
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp
index 82e5c3f583..4c5faa86d4 100644
--- a/apps/openmw/mwgui/alchemywindow.hpp
+++ b/apps/openmw/mwgui/alchemywindow.hpp
@@ -99,6 +99,9 @@ namespace MWGui
std::vector mApparatus;
std::vector mIngredients;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void filterListButtonHandler(const SDL_ControllerButtonEvent& arg);
};
}
diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp
index 3dfdd17627..97405e7cc0 100644
--- a/apps/openmw/mwgui/birth.cpp
+++ b/apps/openmw/mwgui/birth.cpp
@@ -50,15 +50,20 @@ namespace MWGui
mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onAccept);
mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth);
- MyGUI::Button* backButton;
- getWidget(backButton, "BackButton");
- backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onBackClicked);
+ getWidget(mBackButton, "BackButton");
+ mBackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onBackClicked);
- MyGUI::Button* okButton;
- getWidget(okButton, "OKButton");
- okButton->setCaption(
+ getWidget(mOkButton, "OKButton");
+ mOkButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {})));
- okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked);
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mLStick = "#{sMouse}";
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ }
updateBirths();
updateSpells();
@@ -70,8 +75,17 @@ namespace MWGui
getWidget(okButton, "OKButton");
if (shown)
+ {
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {})));
+ mControllerButtons.mX = "#{sNext}";
+ }
+ else if (Settings::gui().mControllerMenus)
+ {
+ okButton->setCaption(
+ MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sDone", {})));
+ mControllerButtons.mX = "#{sDone}";
+ }
else
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {})));
@@ -271,4 +285,30 @@ namespace MWGui
mSpellArea->setVisibleVScroll(true);
mSpellArea->setViewOffset(MyGUI::IntPoint(0, 0));
}
+
+ bool BirthDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onBackClicked(mBackButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkClicked(mOkButton);
+ }
+ 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..b41b1fbb9a 100644
--- a/apps/openmw/mwgui/birth.hpp
+++ b/apps/openmw/mwgui/birth.hpp
@@ -53,8 +53,12 @@ namespace MWGui
MyGUI::ScrollView* mSpellArea;
MyGUI::ImageBox* mBirthImage;
std::vector mSpellItems;
+ MyGUI::Button* mBackButton;
+ MyGUI::Button* mOkButton;
ESM::RefId mCurrentBirthId;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
#endif
diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp
index 47e85b1f4b..ef296534c4 100644
--- a/apps/openmw/mwgui/bookpage.cpp
+++ b/apps/openmw/mwgui/bookpage.cpp
@@ -105,6 +105,18 @@ namespace MWGui
Styles mStyles;
MyGUI::IntRect mRect;
+ void setColour(size_t section, size_t line, size_t run, const MyGUI::Colour& colour) const override
+ {
+ if (section >= mSections.size())
+ return;
+ if (line >= mSections[section].mLines.size())
+ return;
+ if (run >= mSections[section].mLines[line].mRuns.size())
+ return;
+
+ mSections[section].mLines[line].mRuns[run].mStyle->mNormalColour = colour;
+ }
+
virtual ~TypesetBookImpl() {}
Range addContent(const BookTypesetter::Utf8Span& text)
@@ -1288,6 +1300,12 @@ namespace MWGui
void unadviseLinkClicked() override { mPageDisplay->mLinkClicked = std::function(); }
+ void setFocusItem(BookTypesetter::Style* itemStyle) override
+ {
+ mPageDisplay->mFocusItem = static_cast(itemStyle);
+ mPageDisplay->dirtyFocusItem();
+ }
+
protected:
void initialiseOverride() override
{
diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp
index d42fb4783f..bb85130b7f 100644
--- a/apps/openmw/mwgui/bookpage.hpp
+++ b/apps/openmw/mwgui/bookpage.hpp
@@ -31,6 +31,9 @@ namespace MWGui
/// text combined prior to pagination.
virtual std::pair getSize() const = 0;
+ /// Used to highlight journal indices
+ virtual void setColour(size_t section, size_t line, size_t run, const MyGUI::Colour& colour) const = 0;
+
virtual ~TypesetBook() = default;
};
@@ -164,6 +167,8 @@ namespace MWGui
/// Register the widget and associated sub-widget with MyGUI. Should be
/// called once near the beginning of the program.
static void registerMyGUIComponents();
+
+ virtual void setFocusItem(BookTypesetter::Style* itemStyle) = 0;
};
}
diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp
index ef875a18b9..056f1abc89 100644
--- a/apps/openmw/mwgui/bookwindow.cpp
+++ b/apps/openmw/mwgui/bookwindow.cpp
@@ -66,6 +66,10 @@ namespace MWGui
MyGUI::IntCoord(0, 0, (64 - 7) * scale, mNextPageButton->getSize().height * scale));
}
+ mControllerButtons.mL1 = "#{sPrev}";
+ mControllerButtons.mR1 = "#{sNext}";
+ mControllerButtons.mB = "#{Interface:Close}";
+
center();
}
@@ -218,4 +222,26 @@ namespace MWGui
}
}
+ ControllerButtons* BookWindow::getControllerButtons()
+ {
+ mControllerButtons.mA = mTakeButton->getVisible() ? "#{sTake}" : "";
+ return &mControllerButtons;
+ }
+
+ bool BookWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mTakeButton->getVisible())
+ onTakeButtonClicked(mTakeButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCloseButtonClicked(mCloseButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ prevPage();
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ nextPage();
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp
index 5a3dfdf584..7d3a0e30c7 100644
--- a/apps/openmw/mwgui/bookwindow.hpp
+++ b/apps/openmw/mwgui/bookwindow.hpp
@@ -18,8 +18,10 @@ namespace MWGui
void setInventoryAllowed(bool allowed);
void onResChange(int, int) override { center(); }
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
std::string_view getWindowIdForLua() const override { return "Book"; }
+ ControllerButtons* getControllerButtons() override;
protected:
void onNextPageButtonClicked(MyGUI::Widget* sender);
diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp
index 839f0f5072..9573d22377 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,21 @@ 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);
+ mDisableGamepadCursor = true;
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ }
center();
}
@@ -71,6 +78,30 @@ namespace MWGui
center();
}
+ bool GenerateClassResultDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ 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)
@@ -110,13 +141,18 @@ namespace MWGui
getWidget(mClassImage, "ClassImage");
- MyGUI::Button* backButton;
- getWidget(backButton, "BackButton");
- backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onBackClicked);
+ getWidget(mBackButton, "BackButton");
+ mBackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onBackClicked);
- MyGUI::Button* okButton;
- getWidget(okButton, "OKButton");
- okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onOkClicked);
+ getWidget(mOkButton, "OKButton");
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onOkClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mLStick = "#{sMouse}";
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ }
updateClasses();
updateStats();
@@ -128,8 +164,17 @@ namespace MWGui
getWidget(okButton, "OKButton");
if (shown)
+ {
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {})));
+ mControllerButtons.mX = "#{sNext}";
+ }
+ else if (Settings::gui().mControllerMenus)
+ {
+ okButton->setCaption(
+ MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sDone", {})));
+ mControllerButtons.mX = "#{sDone}";
+ }
else
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {})));
@@ -278,6 +323,32 @@ namespace MWGui
setClassImage(mClassImage, mCurrentClassId);
}
+ bool PickClassDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onBackClicked(mBackButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkClicked(mOkButton);
+ }
+ 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)
@@ -312,6 +383,7 @@ namespace MWGui
InfoBoxDialog::InfoBoxDialog()
: WindowModal("openmw_infobox.layout")
+ , mControllerFocus(0)
{
getWidget(mTextBox, "TextBox");
getWidget(mText, "Text");
@@ -319,6 +391,9 @@ namespace MWGui
getWidget(mButtonBar, "ButtonBar");
center();
+
+ mDisableGamepadCursor = Settings::gui().mControllerMenus;
+ mControllerButtons.mA = "#{sSelect}";
}
void InfoBoxDialog::setText(const std::string& str)
@@ -353,6 +428,13 @@ 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);
+ }
+
this->mButtons.push_back(button);
}
}
@@ -382,6 +464,44 @@ namespace MWGui
}
}
+ bool InfoBoxDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus < mButtons.size())
+ onButtonClicked(mButtons[mControllerFocus]);
+ }
+ 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;
+
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus - 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+ if (mButtons.size() == 2 && mControllerFocus == 1)
+ return true;
+
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus + 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+
+ return true;
+ }
+
/* ClassChoiceDialog */
ClassChoiceDialog::ClassChoiceDialog()
@@ -405,6 +525,7 @@ namespace MWGui
: WindowModal("openmw_chargen_create_class.layout")
, mAffectedAttribute(nullptr)
, mAffectedSkill(nullptr)
+ , mControllerFocus(2)
{
// Centre dialog
center();
@@ -450,14 +571,25 @@ namespace MWGui
MyGUI::Button* descriptionButton;
getWidget(descriptionButton, "DescriptionButton");
descriptionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionClicked);
+ mButtons.push_back(descriptionButton);
MyGUI::Button* backButton;
getWidget(backButton, "BackButton");
backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onBackClicked);
+ mButtons.push_back(backButton);
MyGUI::Button* okButton;
getWidget(okButton, "OKButton");
okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onOkClicked);
+ mButtons.push_back(okButton);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ okButton->setStateSelected(true);
+ mControllerButtons.mLStick = "#{sMouse}";
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ }
// Set default skills, attributes
@@ -545,13 +677,56 @@ namespace MWGui
getWidget(okButton, "OKButton");
if (shown)
+ {
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {})));
+ mControllerButtons.mX = "#{sNext}";
+ }
+ else if (Settings::gui().mControllerMenus)
+ {
+ okButton->setCaption(
+ MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sDone", {})));
+ mControllerButtons.mX = "#{sDone}";
+ }
else
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {})));
}
+ bool CreateClassDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus == 0)
+ onDescriptionClicked(mButtons[0]);
+ else if (mControllerFocus == 1)
+ onBackClicked(mButtons[1]);
+ else
+ onOkClicked(mButtons[2]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onBackClicked(mButtons[1]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkClicked(mButtons[2]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ {
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus - 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus + 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+ return true;
+ }
+
// widget controls
void CreateClassDialog::onDialogCancel()
@@ -708,6 +883,9 @@ namespace MWGui
MyGUI::Button* cancelButton;
getWidget(cancelButton, "CancelButton");
cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked);
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
}
SelectSpecializationDialog::~SelectSpecializationDialog() {}
@@ -739,6 +917,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()
@@ -760,6 +948,7 @@ namespace MWGui
widget->setAttributeId(attribute.mId);
widget->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked);
ToolTips::createAttributeToolTip(widget, attribute.mId);
+ mAttributeButtons.emplace_back(widget);
}
attributes->setVisibleVScroll(false);
@@ -770,6 +959,16 @@ namespace MWGui
MyGUI::Button* cancelButton;
getWidget(cancelButton, "CancelButton");
cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ if (mAttributeButtons.size() > 0)
+ mAttributeButtons[0]->setStateSelected(true);
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
}
// widget controls
@@ -791,6 +990,33 @@ namespace MWGui
return true;
}
+ bool SelectAttributeDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus < mAttributeButtons.size())
+ onAttributeClicked(mAttributeButtons[mControllerFocus]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelClicked(nullptr);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ mAttributeButtons[mControllerFocus]->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus - 1, mAttributeButtons.size());
+ mAttributeButtons[mControllerFocus]->setStateSelected(true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ mAttributeButtons[mControllerFocus]->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus + 1, mAttributeButtons.size());
+ mAttributeButtons[mControllerFocus]->setStateSelected(true);
+ }
+
+ return true;
+ }
+
/* SelectSkillDialog */
SelectSkillDialog::SelectSkillDialog()
@@ -820,6 +1046,8 @@ namespace MWGui
skillWidget->setSkillId(skill.mId);
skillWidget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked);
ToolTips::createSkillToolTip(skillWidget, skill.mId);
+ mSkillButtons.emplace_back(skillWidget);
+ mNumSkillsPerSpecialization[skill.mData.mSpecialization]++;
}
for (const auto& [widget, coord] : specializations)
{
@@ -832,6 +1060,16 @@ namespace MWGui
MyGUI::Button* cancelButton;
getWidget(cancelButton, "CancelButton");
cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ if (mSkillButtons.size() > 0)
+ mSkillButtons[0]->setStateSelected(true);
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
}
SelectSkillDialog::~SelectSkillDialog() {}
@@ -855,6 +1093,75 @@ namespace MWGui
return true;
}
+ bool SelectSkillDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus < mSkillButtons.size())
+ onSkillClicked(mSkillButtons[mControllerFocus]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelClicked(nullptr);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ mSkillButtons[mControllerFocus]->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus - 1, mSkillButtons.size());
+ mSkillButtons[mControllerFocus]->setStateSelected(true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ mSkillButtons[mControllerFocus]->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus + 1, mSkillButtons.size());
+ mSkillButtons[mControllerFocus]->setStateSelected(true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT || arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ mSkillButtons[mControllerFocus]->setStateSelected(false);
+ selectNextColumn(arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT ? -1 : 1);
+ mSkillButtons[mControllerFocus]->setStateSelected(true);
+ }
+
+ return true;
+ }
+
+ void SelectSkillDialog::selectNextColumn(int direction)
+ {
+ // Find which column (specialization) the current index is in.
+ size_t specialization = 0;
+ size_t nextSpecializationIndex = 0;
+ for (; specialization < mNumSkillsPerSpecialization.size(); ++specialization)
+ {
+ nextSpecializationIndex += mNumSkillsPerSpecialization[specialization];
+ if (mControllerFocus < nextSpecializationIndex)
+ break;
+ }
+
+ if (direction < 0)
+ {
+ if (mControllerFocus < mNumSkillsPerSpecialization[0])
+ {
+ // Wrap around to the right column
+ for (size_t i = 0; i < mNumSkillsPerSpecialization.size() - 1; ++i)
+ mControllerFocus += mNumSkillsPerSpecialization[i];
+ }
+ else
+ mControllerFocus -= mNumSkillsPerSpecialization[specialization];
+ }
+ else
+ {
+ if (mControllerFocus + mNumSkillsPerSpecialization.back() >= mSkillButtons.size())
+ {
+ // Wrap around to the left column
+ for (size_t i = 0; i < mNumSkillsPerSpecialization.size() - 1; ++i)
+ mControllerFocus -= mNumSkillsPerSpecialization[i];
+ }
+ else
+ mControllerFocus += mNumSkillsPerSpecialization[specialization];
+ }
+ }
+
/* DescriptionDialog */
DescriptionDialog::DescriptionDialog()
@@ -873,6 +1180,8 @@ namespace MWGui
// Make sure the edit box has focus
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit);
+
+ mControllerButtons.mA = "#{Interface:OK}";
}
DescriptionDialog::~DescriptionDialog() {}
@@ -904,4 +1213,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..5a769e15bd 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;
+ size_t mControllerFocus;
};
// 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;
};
@@ -140,11 +146,15 @@ namespace MWGui
MyGUI::ImageBox* mClassImage;
MyGUI::ListBox* mClassList;
MyGUI::TextBox* mSpecializationName;
+ MyGUI::Button* mBackButton;
+ MyGUI::Button* mOkButton;
Widgets::MWAttributePtr mFavoriteAttribute[2];
Widgets::MWSkillPtr mMajorSkill[5];
Widgets::MWSkillPtr mMinorSkill[5];
ESM::RefId mCurrentClassId;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
class SelectSpecializationDialog : public WindowModal
@@ -173,6 +183,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 +217,9 @@ namespace MWGui
protected:
void onAttributeClicked(Widgets::MWAttributePtr _sender);
void onCancelClicked(MyGUI::Widget* _sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ size_t mControllerFocus;
+ std::vector mAttributeButtons;
private:
ESM::RefId mAttributeId;
@@ -237,9 +251,15 @@ namespace MWGui
protected:
void onSkillClicked(Widgets::MWSkillPtr _sender);
void onCancelClicked(MyGUI::Widget* _sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ size_t mControllerFocus;
+ std::vector mSkillButtons;
private:
ESM::RefId mSkillId;
+ std::array mNumSkillsPerSpecialization;
+
+ void selectNextColumn(int direction);
};
class DescriptionDialog : public WindowModal
@@ -258,6 +278,7 @@ namespace MWGui
protected:
void onOkClicked(MyGUI::Widget* _sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
private:
MyGUI::EditBox* mTextEdit;
@@ -314,6 +335,7 @@ namespace MWGui
private:
MyGUI::EditBox* mEditName;
MyGUI::TextBox* mSpecializationName;
+ std::vector mButtons;
Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1;
std::array mMajorSkill;
std::array mMinorSkill;
@@ -329,6 +351,9 @@ namespace MWGui
Widgets::MWAttributePtr mAffectedAttribute;
Widgets::MWSkillPtr mAffectedSkill;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ size_t mControllerFocus;
};
}
#endif
diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp
index 52fc4cc4ce..0972f8e57e 100644
--- a/apps/openmw/mwgui/companionwindow.cpp
+++ b/apps/openmw/mwgui/companionwindow.cpp
@@ -6,6 +6,8 @@
#include
#include
+#include
+
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -61,6 +63,11 @@ namespace MWGui
mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CompanionWindow::onCloseButtonClicked);
setCoord(200, 0, 600, 300);
+
+ mControllerButtons.mA = "#{sTake}";
+ mControllerButtons.mB = "#{Interface:Close}";
+ mControllerButtons.mR3 = "#{sInfo}";
+ mControllerButtons.mL2 = "#{sInventory}";
}
void CompanionWindow::onItemSelected(int index)
@@ -96,13 +103,12 @@ namespace MWGui
name += MWGui::ToolTips::getSoulString(object.getCellRef());
dialog->openCountDialog(name, "#{sTake}", count);
dialog->eventOkClicked.clear();
-
- if (MyGUI::InputManager::getInstance().isAltPressed())
+ if (Settings::gui().mControllerMenus || MyGUI::InputManager::getInstance().isAltPressed())
dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::transferItem);
else
dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem);
}
- else if (MyGUI::InputManager::getInstance().isAltPressed())
+ else if (Settings::gui().mControllerMenus || MyGUI::InputManager::getInstance().isAltPressed())
transferItem(nullptr, count);
else
dragItem(nullptr, count);
@@ -243,4 +249,32 @@ namespace MWGui
{
mItemTransfer->removeTarget(*mItemView);
}
+
+ bool CompanionWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ int index = mItemView->getControllerFocus();
+ if (index >= 0 && index < mItemView->getItemCount())
+ onItemSelected(index);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCloseButtonClicked(mCloseButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK || arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP
+ || arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN || arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT
+ || arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ mItemView->onControllerButton(arg.button);
+ }
+
+ return true;
+ }
+
+ void CompanionWindow::setActiveControllerWindow(bool active)
+ {
+ mItemView->setActiveControllerWindow(active);
+ WindowBase::setActiveControllerWindow(active);
+ }
}
diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp
index 5e78d17334..1b5a772684 100644
--- a/apps/openmw/mwgui/companionwindow.hpp
+++ b/apps/openmw/mwgui/companionwindow.hpp
@@ -40,6 +40,12 @@ namespace MWGui
std::string_view getWindowIdForLua() const override { return "Companion"; }
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void setActiveControllerWindow(bool active) override;
+
+ MWGui::ItemView* getItemView() { return mItemView; }
+ CompanionItemModel* getModel() { return mModel; }
+
private:
ItemView* mItemView;
SortFilterItemModel* mSortModel;
diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp
index 48b209f17e..f23237c896 100644
--- a/apps/openmw/mwgui/confirmationdialog.cpp
+++ b/apps/openmw/mwgui/confirmationdialog.cpp
@@ -3,6 +3,8 @@
#include
#include
+#include
+
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -17,6 +19,13 @@ namespace MWGui
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onCancelButtonClicked);
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mDisableGamepadCursor = true;
+ mControllerButtons.mA = "#{Interface:OK}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
}
void ConfirmationDialog::askForConfirmation(const std::string& message)
@@ -35,6 +44,13 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mOkButton);
+ if (Settings::gui().mControllerMenus)
+ {
+ mOkButtonFocus = true;
+ mOkButton->setStateSelected(true);
+ mCancelButton->setStateSelected(false);
+ }
+
center();
}
@@ -56,4 +72,28 @@ namespace MWGui
eventOkClicked();
}
+
+ bool ConfirmationDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mOkButtonFocus)
+ onOkButtonClicked(mOkButton);
+ else
+ onCancelButtonClicked(mCancelButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelButtonClicked(mCancelButton);
+ }
+ else if ((arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && !mOkButtonFocus)
+ || (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && mOkButtonFocus))
+ {
+ mOkButtonFocus = !mOkButtonFocus;
+ mOkButton->setStateSelected(mOkButtonFocus);
+ mCancelButton->setStateSelected(!mOkButtonFocus);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp
index 1344f2a501..9b26e3a3c9 100644
--- a/apps/openmw/mwgui/confirmationdialog.hpp
+++ b/apps/openmw/mwgui/confirmationdialog.hpp
@@ -27,6 +27,9 @@ namespace MWGui
void onCancelButtonClicked(MyGUI::Widget* _sender);
void onOkButtonClicked(MyGUI::Widget* _sender);
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ bool mOkButtonFocus = true;
};
}
diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp
index 937bab0851..126b6ea1d3 100644
--- a/apps/openmw/mwgui/container.cpp
+++ b/apps/openmw/mwgui/container.cpp
@@ -3,6 +3,8 @@
#include
#include
+#include
+
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
@@ -56,6 +58,12 @@ namespace MWGui
mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked);
setCoord(200, 0, 600, 300);
+
+ mControllerButtons.mA = "#{sTake}";
+ mControllerButtons.mB = "#{Interface:Close}";
+ mControllerButtons.mX = "#{sTakeAll}";
+ mControllerButtons.mR3 = "#{sInfo}";
+ mControllerButtons.mL2 = "#{sInventory}";
}
void ContainerWindow::onItemSelected(int index)
@@ -90,13 +98,12 @@ namespace MWGui
name += MWGui::ToolTips::getSoulString(object.getCellRef());
dialog->openCountDialog(name, "#{sTake}", count);
dialog->eventOkClicked.clear();
-
- if (MyGUI::InputManager::getInstance().isAltPressed())
+ if (Settings::gui().mControllerMenus || MyGUI::InputManager::getInstance().isAltPressed())
dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::transferItem);
else
dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem);
}
- else if (MyGUI::InputManager::getInstance().isAltPressed())
+ else if (Settings::gui().mControllerMenus || MyGUI::InputManager::getInstance().isAltPressed())
transferItem(nullptr, count);
else
dragItem(nullptr, count);
@@ -346,6 +353,49 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container);
}
+ ControllerButtons* ContainerWindow::getControllerButtons()
+ {
+ mControllerButtons.mR1 = mDisposeCorpseButton->getVisible() ? "#{sDisposeofCorpse}" : "";
+ return &mControllerButtons;
+ }
+
+ bool ContainerWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ int index = mItemView->getControllerFocus();
+ if (index >= 0 && index < mItemView->getItemCount())
+ onItemSelected(index);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCloseButtonClicked(mCloseButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onTakeAllButtonClicked(mTakeButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ {
+ if (mDisposeCorpseButton->getVisible())
+ onDisposeCorpseButtonClicked(mDisposeCorpseButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK || arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP
+ || arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN || arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT
+ || arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ mItemView->onControllerButton(arg.button);
+ }
+
+ return true;
+ }
+
+ void ContainerWindow::setActiveControllerWindow(bool active)
+ {
+ mItemView->setActiveControllerWindow(active);
+ WindowBase::setActiveControllerWindow(active);
+ }
+
void ContainerWindow::onFrame(float dt)
{
checkReferenceAvailable();
diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp
index 86ded2ff75..d40507bd82 100644
--- a/apps/openmw/mwgui/container.hpp
+++ b/apps/openmw/mwgui/container.hpp
@@ -48,6 +48,13 @@ namespace MWGui
std::string_view getWindowIdForLua() const override { return "Container"; }
+ ControllerButtons* getControllerButtons() override;
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void setActiveControllerWindow(bool active) override;
+
+ MWGui::ItemView* getItemView() { return mItemView; }
+ ItemModel* getModel() { return mModel; }
+
private:
Misc::NotNullPtr mDragAndDrop;
Misc::NotNullPtr mItemTransfer;
diff --git a/apps/openmw/mwgui/controllerbuttonsoverlay.cpp b/apps/openmw/mwgui/controllerbuttonsoverlay.cpp
new file mode 100644
index 0000000000..1d937c1e63
--- /dev/null
+++ b/apps/openmw/mwgui/controllerbuttonsoverlay.cpp
@@ -0,0 +1,106 @@
+#include "controllerbuttonsoverlay.hpp"
+
+#include
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWGui
+{
+ static constexpr ControllerButtonsOverlay::ButtonDefinition sButtonDefs[] = {
+ { ControllerButtonsOverlay::Button::Button_A, "A", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_A }, &ControllerButtons::mA },
+ { ControllerButtonsOverlay::Button::Button_B, "B", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_B }, &ControllerButtons::mB },
+ { ControllerButtonsOverlay::Button::Button_Dpad, "Dpad", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_DPAD_UP }, &ControllerButtons::mDpad },
+ { ControllerButtonsOverlay::Button::Button_L1, "L1", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_LEFTSHOULDER }, &ControllerButtons::mL1 },
+ { ControllerButtonsOverlay::Button::Button_L2, "L2", ControllerButtonsOverlay::InputType_Axis,
+ { .mAxis = SDL_CONTROLLER_AXIS_TRIGGERLEFT }, &ControllerButtons::mL2 },
+ { ControllerButtonsOverlay::Button::Button_L3, "L3", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_LEFTSTICK }, &ControllerButtons::mL3 },
+ { ControllerButtonsOverlay::Button::Button_LStick, "LStick", ControllerButtonsOverlay::InputType_Axis,
+ { .mAxis = SDL_CONTROLLER_AXIS_LEFTY }, &ControllerButtons::mLStick },
+ { ControllerButtonsOverlay::Button::Button_Menu, "Menu", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_BACK }, &ControllerButtons::mMenu },
+ { ControllerButtonsOverlay::Button::Button_R1, "R1", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER }, &ControllerButtons::mR1 },
+ { ControllerButtonsOverlay::Button::Button_R2, "R2", ControllerButtonsOverlay::InputType_Axis,
+ { .mAxis = SDL_CONTROLLER_AXIS_TRIGGERRIGHT }, &ControllerButtons::mR2 },
+ { ControllerButtonsOverlay::Button::Button_R3, "R3", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_RIGHTSTICK }, &ControllerButtons::mR3 },
+ { ControllerButtonsOverlay::Button::Button_RStick, "RStick", ControllerButtonsOverlay::InputType_Axis,
+ { .mAxis = SDL_CONTROLLER_AXIS_RIGHTY }, &ControllerButtons::mRStick },
+ { ControllerButtonsOverlay::Button::Button_View, "View", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_START }, &ControllerButtons::mView },
+ { ControllerButtonsOverlay::Button::Button_X, "X", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_X }, &ControllerButtons::mX },
+ { ControllerButtonsOverlay::Button::Button_Y, "Y", ControllerButtonsOverlay::InputType_Button,
+ { .mButton = SDL_CONTROLLER_BUTTON_Y }, &ControllerButtons::mY },
+ };
+
+ ControllerButtonsOverlay::ControllerButtonsOverlay()
+ : WindowBase("openmw_controllerbuttons.layout")
+ {
+ MWBase::InputManager* inputMgr = MWBase::Environment::get().getInputManager();
+
+ for (size_t i = 0; i < mButtons.size(); i++)
+ {
+ getWidget(mButtons[i].mImage, "Btn" + sButtonDefs[i].mName + "Image");
+ getWidget(mButtons[i].mText, "Btn" + sButtonDefs[i].mName + "Text");
+ getWidget(mButtons[i].mHBox, "Btn" + sButtonDefs[i].mName);
+
+ if (sButtonDefs[i].mInputType == InputType_Axis)
+ setIcon(mButtons[i].mImage, inputMgr->getControllerAxisIcon(sButtonDefs[i].mId.mAxis));
+ else
+ setIcon(mButtons[i].mImage, inputMgr->getControllerButtonIcon(sButtonDefs[i].mId.mButton));
+ }
+
+ getWidget(mHBox, "ButtonBox");
+ }
+
+ int ControllerButtonsOverlay::getHeight()
+ {
+ MyGUI::Window* window = mMainWidget->castType();
+ return window->getHeight();
+ }
+
+ void ControllerButtonsOverlay::setButtons(ControllerButtons* buttons)
+ {
+ int buttonCount = 0;
+ if (buttons != nullptr)
+ {
+ for (const auto& row : sButtonDefs)
+ buttonCount += updateButton(row.mButton, buttons->*(row.mField));
+
+ mHBox->notifyChildrenSizeChanged();
+ }
+
+ setVisible(buttonCount > 0);
+ }
+
+ void ControllerButtonsOverlay::setIcon(MyGUI::ImageBox* image, const std::string& imagePath)
+ {
+ if (!imagePath.empty())
+ image->setImageTexture(imagePath);
+ }
+
+ int ControllerButtonsOverlay::updateButton(ControllerButtonsOverlay::Button button, const std::string& buttonStr)
+ {
+ if (buttonStr.empty())
+ {
+ mButtons[button].mHBox->setVisible(false);
+ mButtons[button].mHBox->setUserString("Hidden", "true");
+ return 0;
+ }
+ else
+ {
+ mButtons[button].mHBox->setVisible(true);
+ mButtons[button].mHBox->setUserString("Hidden", "false");
+ mButtons[button].mText->setCaptionWithReplacing(buttonStr);
+ return 1;
+ }
+ }
+}
diff --git a/apps/openmw/mwgui/controllerbuttonsoverlay.hpp b/apps/openmw/mwgui/controllerbuttonsoverlay.hpp
new file mode 100644
index 0000000000..cf38505bc2
--- /dev/null
+++ b/apps/openmw/mwgui/controllerbuttonsoverlay.hpp
@@ -0,0 +1,83 @@
+#ifndef MWGUI_CONTROLLERBUTTONSOVERLAY_H
+#define MWGUI_CONTROLLERBUTTONSOVERLAY_H
+
+#include
+#include
+
+#include
+
+#include "windowbase.hpp"
+
+namespace MWGui
+{
+ class ControllerButtonsOverlay : public WindowBase
+ {
+ public:
+ ControllerButtonsOverlay();
+
+ int getHeight();
+ void setButtons(ControllerButtons* buttons);
+
+ enum Button
+ {
+ Button_A = 0,
+ Button_B,
+ Button_Dpad,
+ Button_L1,
+ Button_L2,
+ Button_L3,
+ Button_LStick,
+ Button_Menu,
+ Button_R1,
+ Button_R2,
+ Button_R3,
+ Button_RStick,
+ Button_View,
+ Button_X,
+ Button_Y,
+ Button_Max,
+ };
+
+ enum InputType
+ {
+ InputType_Button,
+ InputType_Axis
+ };
+
+ struct ButtonDefinition
+ {
+ Button mButton;
+ std::string mName;
+ InputType mInputType;
+ union
+ {
+ SDL_GameControllerButton mButton;
+ SDL_GameControllerAxis mAxis;
+ } mId;
+ std::string MWGui::ControllerButtons::*mField;
+ };
+
+ private:
+ struct ButtonWidgets
+ {
+ MyGUI::ImageBox* mImage;
+ MyGUI::TextBox* mText;
+ Gui::HBox* mHBox;
+
+ ButtonWidgets()
+ : mImage(nullptr)
+ , mText(nullptr)
+ , mHBox(nullptr)
+ {
+ }
+ };
+
+ std::array mButtons;
+ Gui::HBox* mHBox;
+
+ void setIcon(MyGUI::ImageBox* image, const std::string& imagePath);
+ int updateButton(Button button, const std::string& buttonStr);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp
index 2ca6657a17..0214d34e8a 100644
--- a/apps/openmw/mwgui/countdialog.cpp
+++ b/apps/openmw/mwgui/countdialog.cpp
@@ -27,6 +27,9 @@ namespace MWGui
mSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &CountDialog::onSliderMoved);
// make sure we read the enter key being pressed to accept multiple items
mItemEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &CountDialog::onEnterKeyPressed);
+
+ mControllerButtons.mA = "#{Interface:OK}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
}
void CountDialog::openCountDialog(const std::string& item, const std::string& message, const int maxCount)
@@ -38,7 +41,7 @@ namespace MWGui
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
mSlider->setScrollRange(maxCount);
- mItemText->setCaption(item);
+ mItemText->setCaptionWithReplacing(item);
int width = std::max(mItemText->getTextSize().width + 160, 320);
setCoord(viewSize.width / 2 - width / 2, viewSize.height / 2 - mMainWidget->getHeight() / 2, width,
@@ -54,6 +57,13 @@ namespace MWGui
mItemEdit->setValue(maxCount);
}
+ void CountDialog::setCount(int count)
+ {
+ count = std::clamp(count, 1, static_cast(mSlider->getScrollRange()));
+ mSlider->setScrollPosition(count - 1);
+ mItemEdit->setValue(count);
+ }
+
void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender)
{
setVisible(false);
@@ -61,17 +71,16 @@ namespace MWGui
void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender)
{
- eventOkClicked(nullptr, mSlider->getScrollPosition() + 1);
-
+ // The order here matters. Hide the dialog first so the OK event tooltips reappear.
setVisible(false);
+ eventOkClicked(nullptr, mSlider->getScrollPosition() + 1);
}
// essentially duplicating what the OK button does if user presses
// Enter key
void CountDialog::onEnterKeyPressed(MyGUI::EditBox* _sender)
{
- eventOkClicked(nullptr, mSlider->getScrollPosition() + 1);
- setVisible(false);
+ onOkButtonClicked(_sender);
// To do not spam onEnterKeyPressed() again and again
MWBase::Environment::get().getWindowManager()->injectKeyRelease(MyGUI::KeyCode::None);
@@ -86,4 +95,22 @@ namespace MWGui
{
mItemEdit->setValue(_position + 1);
}
+
+ bool CountDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ onOkButtonClicked(mOkButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancelButtonClicked(mCancelButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ setCount(1);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ setCount(static_cast(mSlider->getScrollRange()));
+ 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;
+ }
}
diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp
index 70fc820899..0b1e53d167 100644
--- a/apps/openmw/mwgui/countdialog.hpp
+++ b/apps/openmw/mwgui/countdialog.hpp
@@ -15,6 +15,7 @@ namespace MWGui
public:
CountDialog();
void openCountDialog(const std::string& item, const std::string& message, const int maxCount);
+ void setCount(int count);
/** Event : Ok button was clicked.\n
signature : void method(MyGUI::Widget* sender, std::size_t count)\n
@@ -34,6 +35,7 @@ namespace MWGui
void onEditValueChanged(int value);
void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position);
void onEnterKeyPressed(MyGUI::EditBox* _sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp
index 6b1e007770..ec7d726570 100644
--- a/apps/openmw/mwgui/dialogue.cpp
+++ b/apps/openmw/mwgui/dialogue.cpp
@@ -88,6 +88,10 @@ namespace MWGui
mBribe10Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
mBribe100Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
mBribe1000Button->eventMouseButtonClick += MyGUI::newDelegate(this, &PersuasionDialog::onPersuade);
+
+ mDisableGamepadCursor = Settings::gui().mControllerMenus;
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
}
void PersuasionDialog::adjustAction(MyGUI::Widget* action, int& totalHeight)
@@ -144,6 +148,24 @@ namespace MWGui
else
mMainWidget->setSize(mInitialMainWidgetWidth, mMainWidget->getSize().height);
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ mButtons.clear();
+ mButtons.push_back(mAdmireButton);
+ mButtons.push_back(mIntimidateButton);
+ mButtons.push_back(mTauntButton);
+ if (mBribe10Button->getEnabled())
+ mButtons.push_back(mBribe10Button);
+ if (mBribe100Button->getEnabled())
+ mButtons.push_back(mBribe100Button);
+ if (mBribe1000Button->getEnabled())
+ mButtons.push_back(mBribe1000Button);
+
+ for (size_t i = 0; i < mButtons.size(); i++)
+ mButtons[i]->setStateSelected(i == 0);
+ }
+
WindowModal::onOpen();
}
@@ -152,6 +174,31 @@ namespace MWGui
return mAdmireButton;
}
+ bool PersuasionDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ onPersuade(mButtons[mControllerFocus]);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancel(mCancelButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus - 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus + 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+
+ return true;
+ }
+
// --------------------------------------------------------------------------------------------------
Response::Response(std::string_view text, std::string_view title, bool needMargin)
@@ -300,6 +347,9 @@ namespace MWGui
// --------------------------------------------------------------------------------------------------
+ // Morrowind uses 3 px invisible borders for padding topics
+ static constexpr int sVerticalPadding = 3;
+
DialogueWindow::DialogueWindow()
: WindowBase("openmw_dialogue_window.layout")
, mIsCompanion(false)
@@ -335,6 +385,11 @@ namespace MWGui
mMainWidget->castType()->eventWindowChangeCoord
+= MyGUI::newDelegate(this, &DialogueWindow::onWindowResize);
+
+ mControllerScrollWidget = mHistory->getParent();
+ mControllerButtons.mA = "#{sAsk}";
+ mControllerButtons.mB = "#{sGoodbye}";
+ mControllerButtons.mRStick = "#{sScrollup}";
}
void DialogueWindow::onTradeComplete()
@@ -488,6 +543,14 @@ namespace MWGui
updateTopics();
updateTopicsPane(); // force update for new services
+ if (Settings::gui().mControllerMenus && !sameActor)
+ {
+ setControllerFocus(mControllerFocus, false);
+ // Reset focus to very top. Maybe change this to mTopicsList->getItemCount() - mKeywords.size()?
+ mControllerFocus = 0;
+ setControllerFocus(mControllerFocus, true);
+ }
+
updateDisposition();
restock();
}
@@ -543,6 +606,10 @@ namespace MWGui
void DialogueWindow::updateTopicsPane()
{
+ std::string focusedTopic;
+ if (Settings::gui().mControllerMenus && mControllerFocus < static_cast(mTopicsList->getItemCount()))
+ focusedTopic = mTopicsList->getItemNameAt(mControllerFocus);
+
mTopicsList->clear();
for (auto& linkPair : mTopicLinks)
mDeleteLater.push_back(std::move(linkPair.second));
@@ -588,22 +655,25 @@ namespace MWGui
if (mTopicsList->getItemCount() > 0)
mTopicsList->addSeparator();
- // Morrowind uses 3 px invisible borders for padding topics
- constexpr int verticalPadding = 3;
-
for (const auto& keyword : mKeywords)
{
std::string topicId = Misc::StringUtils::lowerCase(keyword);
- mTopicsList->addItem(keyword, verticalPadding);
+ mTopicsList->addItem(keyword, sVerticalPadding);
auto t = std::make_unique(keyword);
mKeywordSearch.seed(topicId, intptr_t(t.get()));
t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated);
mTopicLinks[topicId] = std::move(t);
+
+ if (keyword == focusedTopic)
+ mControllerFocus = mTopicsList->getItemCount() - 1;
}
redrawTopicsList();
updateHistory();
+
+ if (Settings::gui().mControllerMenus)
+ setControllerFocus(mControllerFocus, true);
}
void DialogueWindow::updateHistory(bool scrollbar)
@@ -630,6 +700,8 @@ namespace MWGui
// choices
const TextColours& textColours = MWBase::Environment::get().getWindowManager()->getTextColours();
mChoices = MWBase::Environment::get().getDialogueManager()->getChoices();
+ mChoiceStyles.clear();
+ mControllerChoice = -1; // -1 so you must make a choice (and can't accidentally pick the first answer)
for (std::pair& choice : mChoices)
{
auto link = std::make_unique(choice.second);
@@ -641,6 +713,7 @@ namespace MWGui
BookTypesetter::Style* questionStyle = typesetter->createHotStyle(
body, textColours.answer, textColours.answerOver, textColours.answerPressed, interactiveId);
typesetter->write(questionStyle, to_utf8_span(choice.first));
+ mChoiceStyles.push_back(questionStyle);
}
mGoodbye = MWBase::Environment::get().getDialogueManager()->isGoodbye();
@@ -850,4 +923,120 @@ namespace MWGui
&& actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion");
}
+ void DialogueWindow::setControllerFocus(size_t index, bool focused)
+ {
+ // List is mTopicsList + "Goodbye" button below the list.
+ if (index > mTopicsList->getItemCount())
+ return;
+
+ if (index == mTopicsList->getItemCount())
+ {
+ mGoodbyeButton->setStateSelected(focused);
+ }
+ else
+ {
+ const std::string& keyword = mTopicsList->getItemNameAt(mControllerFocus);
+ if (keyword.empty())
+ return;
+
+ MyGUI::Button* button = mTopicsList->getItemWidget(keyword);
+ button->setStateSelected(focused);
+ }
+
+ if (focused)
+ {
+ // Scroll the side bar to keep the active item in view
+ int offset = 0;
+ for (int i = 6; i < static_cast(index); i++)
+ {
+ const std::string& keyword = mTopicsList->getItemNameAt(i);
+ if (keyword.empty())
+ offset += 18 + sVerticalPadding * 2;
+ else
+ offset += mTopicsList->getItemWidget(keyword)->getHeight() + sVerticalPadding * 2;
+ }
+ mTopicsList->setViewOffset(-offset);
+ }
+ }
+
+ bool DialogueWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mChoices.size() > 0)
+ {
+ if (mChoices.size() == 1)
+ onChoiceActivated(mChoices[0].second);
+ else if (mControllerChoice >= 0 && mControllerChoice < static_cast(mChoices.size()))
+ onChoiceActivated(mChoices[mControllerChoice].second);
+ }
+ else if (mControllerFocus == static_cast(mTopicsList->getItemCount()))
+ onGoodbyeActivated();
+ else
+ onSelectListItem(mTopicsList->getItemNameAt(mControllerFocus), mControllerFocus);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B && mChoices.empty())
+ {
+ onGoodbyeActivated();
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ if (mChoices.size() > 0)
+ {
+ // In-dialogue choice (red text)
+ mControllerChoice = std::clamp(mControllerChoice - 1, 0, static_cast(mChoices.size()) - 1);
+ mHistory->setFocusItem(mChoiceStyles.at(mControllerChoice));
+ }
+ else
+ {
+ // Number of items is mTopicsList.length+1 because of "Goodbye" button.
+ setControllerFocus(mControllerFocus, false);
+ if (mControllerFocus <= 0)
+ mControllerFocus = mTopicsList->getItemCount(); // "Goodbye" button
+ else if (mTopicsList->getItemNameAt(mControllerFocus - 1).empty())
+ mControllerFocus -= 2; // Skip separator
+ else
+ mControllerFocus--;
+ setControllerFocus(mControllerFocus, true);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (mChoices.size() > 0)
+ {
+ // In-dialogue choice (red text)
+ mControllerChoice = std::clamp(mControllerChoice + 1, 0, static_cast(mChoices.size()) - 1);
+ mHistory->setFocusItem(mChoiceStyles.at(mControllerChoice));
+ }
+ else
+ {
+ // Number of items is mTopicsList.length+1 because of "Goodbye" button.
+ setControllerFocus(mControllerFocus, false);
+ if (mControllerFocus >= static_cast(mTopicsList->getItemCount()))
+ mControllerFocus = 0;
+ else if (mControllerFocus == static_cast(mTopicsList->getItemCount()) - 1)
+ mControllerFocus = mTopicsList->getItemCount(); // "Goodbye" button
+ else if (mTopicsList->getItemNameAt(mControllerFocus + 1).empty())
+ mControllerFocus += 2; // Skip separator
+ else
+ mControllerFocus++;
+ setControllerFocus(mControllerFocus, true);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER && mChoices.size() == 0)
+ {
+ setControllerFocus(mControllerFocus, false);
+ mControllerFocus = std::max(mControllerFocus - 5, 0);
+ setControllerFocus(mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER && mChoices.size() == 0)
+ {
+ setControllerFocus(mControllerFocus, false);
+ mControllerFocus = std::min(mControllerFocus + 5, static_cast(mTopicsList->getItemCount()));
+ setControllerFocus(mControllerFocus, true);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp
index 8a8b309401..6f03076e92 100644
--- a/apps/openmw/mwgui/dialogue.hpp
+++ b/apps/openmw/mwgui/dialogue.hpp
@@ -49,6 +49,9 @@ namespace MWGui
MyGUI::Widget* getDefaultKeyFocus() override;
+ protected:
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+
private:
std::unique_ptr mCallback;
@@ -65,6 +68,9 @@ namespace MWGui
MyGUI::Widget* mActionsBox;
Gui::AutoSizedTextBox* mGoldLabel;
+ std::vector mButtons;
+ int mControllerFocus = 0;
+
void adjustAction(MyGUI::Widget* action, int& totalHeight);
void onCancel(MyGUI::Widget* sender);
@@ -186,6 +192,8 @@ namespace MWGui
void onReferenceUnavailable() override;
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+
private:
void updateDisposition();
void restock();
@@ -197,6 +205,7 @@ namespace MWGui
std::vector> mHistoryContents;
std::vector> mChoices;
+ std::vector mChoiceStyles;
bool mGoodbye;
std::vector> mLinks;
@@ -220,6 +229,10 @@ namespace MWGui
std::unique_ptr mCallback;
std::unique_ptr mGreetingCallback;
+ void setControllerFocus(size_t index, bool focused);
+ int mControllerFocus = 0;
+ int mControllerChoice = -1;
+
void updateTopicFormat();
};
}
diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp
index af4a3e8ce3..d6a42f8ea0 100644
--- a/apps/openmw/mwgui/enchantingdialog.cpp
+++ b/apps/openmw/mwgui/enchantingdialog.cpp
@@ -59,6 +59,12 @@ namespace MWGui
mBuyButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onBuyButtonClicked);
mTypeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onTypeButtonClicked);
mName->eventEditSelectAccept += MyGUI::newDelegate(this, &EnchantingDialog::onAccept);
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mY = "#{OMWEngine:EnchantType}";
+ mControllerButtons.mL1 = "#{sItem}";
+ mControllerButtons.mR1 = "#{sSoulGem}";
}
void EnchantingDialog::onOpen()
@@ -152,6 +158,7 @@ namespace MWGui
mEnchanting.setSelfEnchanting(false);
mEnchanting.setEnchanter(ptr);
mBuyButton->setCaptionWithReplacing("#{sBuy}");
+ mControllerButtons.mX = "#{sBuy}";
mChanceLayout->setVisible(false);
mPtr = ptr;
setSoulGem(MWWorld::Ptr());
@@ -163,6 +170,7 @@ namespace MWGui
mEnchanting.setSelfEnchanting(true);
mEnchanting.setEnchanter(MWMechanics::getPlayer());
mBuyButton->setCaptionWithReplacing("#{sCreate}");
+ mControllerButtons.mX = "#{sCreate}";
mChanceLayout->setVisible(Settings::game().mShowEnchantChance);
mPtr = MWMechanics::getPlayer();
setSoulGem(ptr);
@@ -382,4 +390,22 @@ namespace MWGui
}
}
}
+
+ bool EnchantingDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancelButtonClicked(mCancelButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ onBuyButtonClicked(mBuyButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y)
+ onTypeButtonClicked(mTypeButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ onSelectItem(mItemBox);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ onSelectSoul(mSoulBox);
+ else
+ return EffectEditorBase::onControllerButtonEvent(arg);
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp
index 4c720a11fc..3cda350152 100644
--- a/apps/openmw/mwgui/enchantingdialog.hpp
+++ b/apps/openmw/mwgui/enchantingdialog.hpp
@@ -73,6 +73,8 @@ namespace MWGui
MWMechanics::Enchanting mEnchanting;
ESM::EffectList mEffectList;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp
index e5adce7624..4983836551 100644
--- a/apps/openmw/mwgui/hud.cpp
+++ b/apps/openmw/mwgui/hud.cpp
@@ -188,6 +188,17 @@ namespace MWGui
mDrowningBar->setVisible(visible);
}
+ void HUD::dropDraggedItem(float mouseX, float mouseY)
+ {
+ if (!mDragAndDrop->mIsOnDragAndDrop)
+ return;
+
+ MWBase::Environment::get().getWorld()->breakInvisibility(MWMechanics::getPlayer());
+
+ WorldItemModel drop(mouseX, mouseY);
+ mDragAndDrop->drop(&drop, nullptr);
+ }
+
void HUD::onWorldClicked(MyGUI::Widget* _sender)
{
if (!MWBase::Environment::get().getWindowManager()->isGuiMode())
diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp
index 8dd98628c4..1a1076ff68 100644
--- a/apps/openmw/mwgui/hud.hpp
+++ b/apps/openmw/mwgui/hud.hpp
@@ -61,6 +61,8 @@ namespace MWGui
void clear() override;
+ void dropDraggedItem(float mouseX, float mouseY);
+
private:
MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning;
MyGUI::Widget* mHealthFrame;
diff --git a/apps/openmw/mwgui/inventorytabsoverlay.cpp b/apps/openmw/mwgui/inventorytabsoverlay.cpp
new file mode 100644
index 0000000000..d95079ab53
--- /dev/null
+++ b/apps/openmw/mwgui/inventorytabsoverlay.cpp
@@ -0,0 +1,63 @@
+#include "inventorytabsoverlay.hpp"
+
+#include
+#include
+#include
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
+namespace MWGui
+{
+ InventoryTabsOverlay::InventoryTabsOverlay()
+ : WindowBase("openmw_inventory_tabs.layout")
+ {
+ MyGUI::Button* tab;
+ static const char* kTabIds[] = { "TabMap", "TabInventory", "TabSpells", "TabStats" };
+
+ for (const char* id : kTabIds)
+ {
+ getWidget(tab, id);
+ tab->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryTabsOverlay::onTabClicked);
+ mTabs.push_back(tab);
+ }
+
+ MyGUI::ImageBox* image;
+ getWidget(image, "BtnL2Image");
+ image->setImageTexture(
+ MWBase::Environment::get().getInputManager()->getControllerAxisIcon(SDL_CONTROLLER_AXIS_TRIGGERLEFT));
+
+ getWidget(image, "BtnR2Image");
+ image->setImageTexture(
+ MWBase::Environment::get().getInputManager()->getControllerAxisIcon(SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
+ }
+
+ int InventoryTabsOverlay::getHeight()
+ {
+ MyGUI::Window* window = mMainWidget->castType();
+ return window->getHeight();
+ }
+
+ void InventoryTabsOverlay::onTabClicked(MyGUI::Widget* sender)
+ {
+ if (!MWBase::Environment::get().getWindowManager()->getJournalAllowed())
+ return;
+
+ for (int i = 0; i < static_cast(mTabs.size()); i++)
+ {
+ if (mTabs[i] == sender)
+ {
+ MWBase::Environment::get().getWindowManager()->setActiveControllerWindow(GM_Inventory, i);
+ setTab(i);
+ break;
+ }
+ }
+ }
+
+ void InventoryTabsOverlay::setTab(int index)
+ {
+ for (int i = 0; i < static_cast(mTabs.size()); i++)
+ mTabs[i]->setStateSelected(i == index);
+ }
+}
diff --git a/apps/openmw/mwgui/inventorytabsoverlay.hpp b/apps/openmw/mwgui/inventorytabsoverlay.hpp
new file mode 100644
index 0000000000..26544aa310
--- /dev/null
+++ b/apps/openmw/mwgui/inventorytabsoverlay.hpp
@@ -0,0 +1,28 @@
+#ifndef MWGUI_INVENTORYTABSSOVERLAY_H
+#define MWGUI_INVENTORYTABSSOVERLAY_H
+
+#include "windowbase.hpp"
+
+namespace MyGUI
+{
+ class Button;
+}
+
+namespace MWGui
+{
+ class InventoryTabsOverlay : public WindowBase
+ {
+ public:
+ InventoryTabsOverlay();
+
+ int getHeight();
+ void setTab(int index);
+
+ private:
+ std::vector mTabs;
+
+ void onTabClicked(MyGUI::Widget* sender);
+ };
+}
+
+#endif
diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp
index da30fa86ff..97dbe64e76 100644
--- a/apps/openmw/mwgui/inventorywindow.cpp
+++ b/apps/openmw/mwgui/inventorywindow.cpp
@@ -19,6 +19,7 @@
#include
#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -31,13 +32,17 @@
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/npcstats.hpp"
+#include "companionwindow.hpp"
+#include "container.hpp"
#include "countdialog.hpp"
#include "draganddrop.hpp"
+#include "hud.hpp"
#include "inventoryitemmodel.hpp"
#include "itemtransfer.hpp"
#include "itemview.hpp"
#include "settings.hpp"
#include "sortfilteritemmodel.hpp"
+#include "statswindow.hpp"
#include "tooltips.hpp"
#include "tradeitemmodel.hpp"
#include "tradewindow.hpp"
@@ -89,6 +94,7 @@ namespace MWGui
, mPreview(std::make_unique(parent, resourceSystem, MWMechanics::getPlayer()))
, mTrading(false)
, mUpdateNextFrame(false)
+ , mPendingControllerAction(ControllerAction::None)
{
mPreviewTexture
= std::make_unique(mPreview->getTexture(), mPreview->getTextureStateSet());
@@ -129,6 +135,25 @@ namespace MWGui
setGuiMode(mGuiMode);
+ if (Settings::gui().mControllerMenus)
+ {
+ // Show L1 and R1 buttons next to tabs
+ MyGUI::ImageBox* image;
+ getWidget(image, "BtnL1Image");
+ image->setVisible(true);
+ image->setUserString("Hidden", "false");
+ image->setImageTexture(MWBase::Environment::get().getInputManager()->getControllerButtonIcon(
+ SDL_CONTROLLER_BUTTON_LEFTSHOULDER));
+
+ getWidget(image, "BtnR1Image");
+ image->setVisible(true);
+ image->setUserString("Hidden", "false");
+ image->setImageTexture(MWBase::Environment::get().getInputManager()->getControllerButtonIcon(
+ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER));
+
+ mControllerButtons.mR3 = "#{sInfo}";
+ }
+
adjustPanes();
}
@@ -208,9 +233,13 @@ namespace MWGui
void InventoryWindow::setGuiMode(GuiMode mode)
{
+ if (Settings::gui().mControllerMenus && mGuiMode == mode && isVisible())
+ return;
+
mGuiMode = mode;
const WindowSettingValues settings = getModeSettings(mGuiMode);
- setPinButtonVisible(mode != GM_Container && mode != GM_Companion && mode != GM_Barter);
+ setPinButtonVisible(
+ mode != GM_Container && mode != GM_Companion && mode != GM_Barter && !Settings::gui().mControllerMenus);
const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular;
@@ -306,18 +335,26 @@ namespace MWGui
}
}
- if (count > 1 && !shift)
+ // Show a dialog to select a count of items, but not when using an item from the inventory
+ // in controller mode. In that case, we skip the dialog and just use one item immediately.
+ if (count > 1 && !shift && mPendingControllerAction != ControllerAction::Use)
{
CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
- std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}";
+ std::string message = "#{sTake}";
+ if (mTrading || mPendingControllerAction == ControllerAction::Sell)
+ message = "#{sQuanityMenuMessage01}";
+ else if (mPendingControllerAction == ControllerAction::Drop)
+ message = "#{sDrop}";
std::string name{ object.getClass().getName(object) };
name += MWGui::ToolTips::getSoulString(object.getCellRef());
dialog->openCountDialog(name, message, count);
dialog->eventOkClicked.clear();
-
- if (mTrading)
+ if (mTrading || mPendingControllerAction == ControllerAction::Sell)
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem);
- else if (MyGUI::InputManager::getInstance().isAltPressed())
+ else if (mPendingControllerAction == ControllerAction::Drop)
+ dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dropItem);
+ else if (MyGUI::InputManager::getInstance().isAltPressed()
+ || mPendingControllerAction == ControllerAction::Transfer)
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::transferItem);
else
dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::dragItem);
@@ -328,13 +365,35 @@ namespace MWGui
{
mSelectedItem = index;
- if (mTrading)
+ if (mTrading || mPendingControllerAction == ControllerAction::Sell)
sellItem(nullptr, count);
- else if (MyGUI::InputManager::getInstance().isAltPressed())
+ else if (mPendingControllerAction == ControllerAction::Use)
+ {
+ dragItem(nullptr, count);
+ if (item.mType == ItemStack::Type_Equipped)
+ {
+ // Drop the item on the inventory background to unequip it.
+ onBackgroundSelected();
+ }
+ else
+ {
+ // Drop the item on the avatar to activate or equip it.
+ onAvatarClicked(nullptr);
+ // Drop any remaining items back in inventory. This is needed when clicking on a
+ // stack of items; we only want to use the first item.
+ onBackgroundSelected();
+ }
+ }
+ else if (mPendingControllerAction == ControllerAction::Drop)
+ dropItem(nullptr, count);
+ else if (MyGUI::InputManager::getInstance().isAltPressed()
+ || mPendingControllerAction == ControllerAction::Transfer)
transferItem(nullptr, count);
else
dragItem(nullptr, count);
}
+
+ mPendingControllerAction = ControllerAction::None;
}
void InventoryWindow::ensureSelectedItemUnequipped(int count)
@@ -408,6 +467,19 @@ namespace MWGui
notifyContentChanged();
}
+ void InventoryWindow::dropItem(MyGUI::Widget* sender, size_t count)
+ {
+ if (mGuiMode != MWGui::GM_Inventory)
+ return;
+
+ if (!mDragAndDrop->mIsOnDragAndDrop)
+ dragItem(sender, count);
+
+ // Drop the item into the gameworld
+ if (mDragAndDrop->mIsOnDragAndDrop)
+ MWBase::Environment::get().getWindowManager()->getHud()->dropDraggedItem(0.5f, 0.5f);
+ }
+
void InventoryWindow::updateItemView()
{
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
@@ -523,7 +595,9 @@ namespace MWGui
void InventoryWindow::onTitleDoubleClicked()
{
- if (MyGUI::InputManager::getInstance().isShiftPressed())
+ if (Settings::gui().mControllerMenus && mGuiMode == GM_Inventory)
+ return;
+ else if (MyGUI::InputManager::getInstance().isShiftPressed())
toggleMaximized();
else if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory);
@@ -890,4 +964,151 @@ namespace MWGui
const MyGUI::IntSize viewport = getPreviewViewportSize();
return osg::Vec2f(normalisedX * float(viewport.width - 1), (1.0 - normalisedY) * float(viewport.height - 1));
}
+
+ ControllerButtons* InventoryWindow::getControllerButtons()
+ {
+ switch (mGuiMode)
+ {
+ case MWGui::GM_Companion:
+ mControllerButtons.mA = "#{OMWEngine:InventorySelect}";
+ mControllerButtons.mB = "#{Interface:Close}";
+ mControllerButtons.mX.clear();
+ mControllerButtons.mR2 = "#{sCompanionShare}";
+ break;
+ case MWGui::GM_Container:
+ mControllerButtons.mA = "#{OMWEngine:InventorySelect}";
+ mControllerButtons.mB = "#{Interface:Close}";
+ mControllerButtons.mX = "#{sTakeAll}";
+ mControllerButtons.mR2 = "#{sContainer}";
+ break;
+ case MWGui::GM_Barter:
+ mControllerButtons.mA = "#{sSell}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mX = "#{sOffer}";
+ mControllerButtons.mR2 = "#{sBarter}";
+ break;
+ case MWGui::GM_Inventory:
+ default:
+ mControllerButtons.mA = "#{sEquip}";
+ mControllerButtons.mB = "#{sBack}";
+ mControllerButtons.mX = "#{sDrop}";
+ mControllerButtons.mR2.clear();
+ break;
+ }
+ return &mControllerButtons;
+ }
+
+ bool InventoryWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ mPendingControllerAction = ControllerAction::None; // Clear any pending controller actions
+
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mGuiMode == MWGui::GM_Inventory)
+ mPendingControllerAction = ControllerAction::Use;
+ else if (mGuiMode == MWGui::GM_Companion || mGuiMode == MWGui::GM_Container)
+ mPendingControllerAction = ControllerAction::Transfer;
+ else if (mGuiMode == MWGui::GM_Barter)
+ mPendingControllerAction = ControllerAction::Sell;
+
+ mItemView->onControllerButton(SDL_CONTROLLER_BUTTON_A);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ if (mGuiMode == MWGui::GM_Inventory)
+ {
+ mPendingControllerAction = ControllerAction::Drop;
+ mItemView->onControllerButton(SDL_CONTROLLER_BUTTON_A);
+ }
+ else if (mGuiMode == MWGui::GM_Container)
+ {
+ // Take all. Pass the button press to the container window and let it do the
+ // logic of taking all.
+ MWGui::ContainerWindow* containerWindow = static_cast(
+ MWBase::Environment::get().getWindowManager()->getGuiModeWindows(mGuiMode).at(0));
+ containerWindow->onControllerButtonEvent(arg);
+ }
+ else if (mGuiMode == MWGui::GM_Barter)
+ {
+ // Offer. Pass the button press to the barter window and let it do the logic
+ // of making an offer.
+ MWGui::TradeWindow* tradeWindow = static_cast(
+ MWBase::Environment::get().getWindowManager()->getGuiModeWindows(mGuiMode).at(1));
+ tradeWindow->onControllerButtonEvent(arg);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ {
+ if (mFilterAll->getStateSelected())
+ onFilterChanged(mFilterMisc);
+ else if (mFilterWeapon->getStateSelected())
+ onFilterChanged(mFilterAll);
+ else if (mFilterApparel->getStateSelected())
+ onFilterChanged(mFilterWeapon);
+ else if (mFilterMagic->getStateSelected())
+ onFilterChanged(mFilterApparel);
+ else if (mFilterMisc->getStateSelected())
+ onFilterChanged(mFilterMagic);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ {
+ if (mFilterAll->getStateSelected())
+ onFilterChanged(mFilterWeapon);
+ else if (mFilterWeapon->getStateSelected())
+ onFilterChanged(mFilterApparel);
+ else if (mFilterApparel->getStateSelected())
+ onFilterChanged(mFilterMagic);
+ else if (mFilterMagic->getStateSelected())
+ onFilterChanged(mFilterMisc);
+ else if (mFilterMisc->getStateSelected())
+ onFilterChanged(mFilterAll);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else
+ {
+ mItemView->onControllerButton(arg.button);
+ }
+
+ return true;
+ }
+
+ void InventoryWindow::setActiveControllerWindow(bool active)
+ {
+ if (!Settings::gui().mControllerMenus)
+ return;
+
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+ if (winMgr->getMode() == MWGui::GM_Inventory)
+ {
+ // Fill the screen, or limit to a certain size on large screens. Size chosen to
+ // match the size of the stats window.
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ int width = std::min(viewSize.width, 1600);
+ int height = std::min(winMgr->getControllerMenuHeight(), StatsWindow::getIdealHeight());
+ int x = (viewSize.width - width) / 2;
+ int y = (viewSize.height - height) / 2;
+
+ MyGUI::Window* window = mMainWidget->castType();
+ window->setCoord(x, active ? y : viewSize.height + 1, width, height);
+
+ adjustPanes();
+ updatePreviewSize();
+ }
+
+ // Show L1 and R1 buttons next to tabs
+ MyGUI::Widget* image;
+ getWidget(image, "BtnL1Image");
+ image->setVisible(active);
+
+ getWidget(image, "BtnR1Image");
+ image->setVisible(active);
+
+ mItemView->setActiveControllerWindow(active);
+ WindowBase::setActiveControllerWindow(active);
+ }
}
diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp
index e245fe46ca..ce02a83c1b 100644
--- a/apps/openmw/mwgui/inventorywindow.hpp
+++ b/apps/openmw/mwgui/inventorywindow.hpp
@@ -77,8 +77,12 @@ namespace MWGui
std::string_view getWindowIdForLua() const override { return "Inventory"; }
+ ControllerButtons* getControllerButtons() override;
+
protected:
void onTitleDoubleClicked() override;
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void setActiveControllerWindow(bool active) override;
private:
Misc::NotNullPtr mDragAndDrop;
@@ -126,9 +130,20 @@ namespace MWGui
void onBackgroundSelected();
+ enum class ControllerAction
+ {
+ None,
+ Use,
+ Transfer,
+ Sell,
+ Drop,
+ };
+ ControllerAction mPendingControllerAction;
+
void sellItem(MyGUI::Widget* sender, std::size_t count);
void dragItem(MyGUI::Widget* sender, std::size_t count);
void transferItem(MyGUI::Widget* sender, std::size_t count);
+ void dropItem(MyGUI::Widget* sender, std::size_t count);
void onWindowResize(MyGUI::Window* _sender);
void onFilterChanged(MyGUI::Widget* _sender);
diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp
index 02c3cc182c..59731717e1 100644
--- a/apps/openmw/mwgui/itemchargeview.cpp
+++ b/apps/openmw/mwgui/itemchargeview.cpp
@@ -1,16 +1,17 @@
#include "itemchargeview.hpp"
+#include
#include
#include
#include
#include
-#include
-#include
#include
+#include
#include "../mwbase/environment.hpp"
+#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/spellutil.hpp"
@@ -19,6 +20,8 @@
#include "itemmodel.hpp"
#include "itemwidget.hpp"
+#include "textcolours.hpp"
+#include "windowbase.hpp"
namespace MWGui
{
@@ -156,11 +159,20 @@ namespace MWGui
mScrollView->setCanvasSize(
MyGUI::IntSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), currentY)));
mScrollView->setVisibleVScroll(true);
+
+ if (Settings::gui().mControllerMenus)
+ updateControllerFocus(-1, mControllerFocus);
}
void ItemChargeView::resetScrollbars()
{
mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+
+ if (Settings::gui().mControllerMenus)
+ {
+ updateControllerFocus(mControllerFocus, 0);
+ mControllerFocus = 0;
+ }
}
void ItemChargeView::setSize(const MyGUI::IntSize& value)
@@ -224,4 +236,52 @@ namespace MWGui
mScrollView->setViewOffset(
MyGUI::IntPoint(0, static_cast(mScrollView->getViewOffset().top + rel * 0.3f)));
}
+
+ void ItemChargeView::onControllerButton(const unsigned char button)
+ {
+ if (mLines.empty())
+ return;
+
+ int prevFocus = mControllerFocus;
+
+ if (button == SDL_CONTROLLER_BUTTON_A)
+ {
+ // Select the focused item, if any.
+ if (mControllerFocus >= 0 && mControllerFocus < static_cast(mLines.size()))
+ onIconClicked(mLines[mControllerFocus].mIcon);
+ }
+ else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ mControllerFocus = wrap(mControllerFocus - 1, mLines.size());
+ else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ mControllerFocus = wrap(mControllerFocus + 1, mLines.size());
+
+ if (prevFocus != mControllerFocus)
+ updateControllerFocus(prevFocus, mControllerFocus);
+ }
+
+ void ItemChargeView::updateControllerFocus(int prevFocus, int newFocus)
+ {
+ if (mLines.empty())
+ return;
+
+ const TextColours& textColours{ MWBase::Environment::get().getWindowManager()->getTextColours() };
+
+ if (prevFocus >= 0 && prevFocus < static_cast(mLines.size()))
+ {
+ mLines[prevFocus].mText->setTextColour(textColours.normal);
+ mLines[prevFocus].mIcon->setControllerFocus(false);
+ }
+
+ if (newFocus >= 0 && newFocus < static_cast(mLines.size()))
+ {
+ mLines[newFocus].mText->setTextColour(textColours.link);
+ mLines[newFocus].mIcon->setControllerFocus(true);
+
+ // Scroll the list to keep the active item in view
+ if (newFocus <= 3)
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, -55 * (newFocus - 3)));
+ }
+ }
}
diff --git a/apps/openmw/mwgui/itemchargeview.hpp b/apps/openmw/mwgui/itemchargeview.hpp
index f7617d37eb..73bf9c3de2 100644
--- a/apps/openmw/mwgui/itemchargeview.hpp
+++ b/apps/openmw/mwgui/itemchargeview.hpp
@@ -52,6 +52,8 @@ namespace MWGui
MyGUI::delegates::MultiDelegate eventItemClicked;
+ void onControllerButton(const unsigned char button);
+
private:
struct Line
{
@@ -72,6 +74,9 @@ namespace MWGui
std::unique_ptr mModel;
MyGUI::ScrollView* mScrollView;
DisplayMode mDisplayMode;
+
+ int mControllerFocus;
+ void updateControllerFocus(int prevFocus, int newFocus);
};
}
diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp
index 4fe40ce693..30d8dbfb60 100644
--- a/apps/openmw/mwgui/itemselection.cpp
+++ b/apps/openmw/mwgui/itemselection.cpp
@@ -3,6 +3,8 @@
#include
#include
+#include
+
#include "inventoryitemmodel.hpp"
#include "itemview.hpp"
#include "sortfilteritemmodel.hpp"
@@ -26,6 +28,10 @@ namespace MWGui
cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemSelectionDialog::onCancelButtonClicked);
center();
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mR3 = "#{sInfo}";
}
bool ItemSelectionDialog::exit()
@@ -40,6 +46,8 @@ namespace MWGui
mSortModel = sortModel.get();
mItemView->setModel(std::move(sortModel));
mItemView->resetScrollBars();
+ if (Settings::gui().mControllerMenus)
+ mItemView->setActiveControllerWindow(true);
}
void ItemSelectionDialog::setCategory(int category)
@@ -65,4 +73,13 @@ namespace MWGui
exit();
}
+ bool ItemSelectionDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancelButtonClicked(nullptr);
+ else
+ mItemView->onControllerButton(arg.button);
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp
index fe87d7e38a..83af6d4840 100644
--- a/apps/openmw/mwgui/itemselection.hpp
+++ b/apps/openmw/mwgui/itemselection.hpp
@@ -41,6 +41,7 @@ namespace MWGui
void onSelectedItem(int index);
void onCancelButtonClicked(MyGUI::Widget* sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp
index ff05a8b2d6..243837bb65 100644
--- a/apps/openmw/mwgui/itemview.cpp
+++ b/apps/openmw/mwgui/itemview.cpp
@@ -7,6 +7,12 @@
#include
#include
+#include
+
+#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
#include "itemmodel.hpp"
#include "itemwidget.hpp"
@@ -15,6 +21,7 @@ namespace MWGui
ItemView::ItemView()
: mScrollView(nullptr)
+ , mControllerActiveWindow(false)
{
}
@@ -46,13 +53,16 @@ namespace MWGui
MyGUI::Widget* dragArea = mScrollView->getChildAt(0);
int maxHeight = mScrollView->getHeight();
- int rows = maxHeight / 42;
- rows = std::max(rows, 1);
- bool showScrollbar = int(std::ceil(dragArea->getChildCount() / float(rows))) > mScrollView->getWidth() / 42;
+ mRows = std::max(maxHeight / 42, 1);
+ mItemCount = dragArea->getChildCount();
+ bool showScrollbar = static_cast(std::ceil(mItemCount / float(mRows))) > mScrollView->getWidth() / 42;
if (showScrollbar)
+ {
maxHeight -= 18;
+ mRows = std::max(maxHeight / 42, 1);
+ }
- for (unsigned int i = 0; i < dragArea->getChildCount(); ++i)
+ for (int i = 0; i < mItemCount; ++i)
{
MyGUI::Widget* w = dragArea->getChildAt(i);
@@ -60,7 +70,7 @@ namespace MWGui
y += 42;
- if (y > maxHeight - 42 && i < dragArea->getChildCount() - 1)
+ if (y > maxHeight - 42 && i < mItemCount - 1)
{
x += 42;
y = 0;
@@ -70,6 +80,12 @@ namespace MWGui
MyGUI::IntSize size = MyGUI::IntSize(std::max(mScrollView->getSize().width, x), mScrollView->getSize().height);
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = std::clamp(mControllerFocus, 0, mItemCount - 1);
+ updateControllerFocus(-1, mControllerFocus);
+ }
+
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the
// scrollbar is hidden
mScrollView->setVisibleVScroll(false);
@@ -122,6 +138,11 @@ namespace MWGui
void ItemView::resetScrollBars()
{
mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+ if (Settings::gui().mControllerMenus)
+ {
+ updateControllerFocus(mControllerFocus, 0);
+ mControllerFocus = 0;
+ }
}
void ItemView::onSelectedItem(MyGUI::Widget* sender)
@@ -165,4 +186,108 @@ namespace MWGui
MyGUI::FactoryManager::getInstance().registerFactory("Widget");
}
+ void ItemView::setActiveControllerWindow(bool active)
+ {
+ mControllerActiveWindow = active;
+
+ MWBase::Environment::get().getWindowManager()->setControllerTooltip(
+ active && Settings::gui().mControllerTooltips);
+
+ if (active)
+ updateControllerFocus(-1, mControllerFocus);
+ else
+ updateControllerFocus(mControllerFocus, -1);
+ }
+
+ void ItemView::onControllerButton(const unsigned char button)
+ {
+ if (!mItemCount)
+ return;
+
+ int prevFocus = mControllerFocus;
+
+ switch (button)
+ {
+ case SDL_CONTROLLER_BUTTON_A:
+ // Select the focused item, if any.
+ if (mControllerFocus >= 0 && mControllerFocus < mItemCount)
+ {
+ MyGUI::Widget* dragArea = mScrollView->getChildAt(0);
+ onSelectedItem(dragArea->getChildAt(mControllerFocus));
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
+ // Toggle info tooltip
+ MWBase::Environment::get().getWindowManager()->setControllerTooltip(
+ !MWBase::Environment::get().getWindowManager()->getControllerTooltip());
+ updateControllerFocus(-1, mControllerFocus);
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_UP:
+ if (mControllerFocus % mRows == 0)
+ mControllerFocus = std::min(mControllerFocus + mRows - 1, mItemCount - 1);
+ else
+ mControllerFocus--;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
+ if (mControllerFocus % mRows == mRows - 1 || mControllerFocus == mItemCount - 1)
+ mControllerFocus -= mControllerFocus % mRows;
+ else
+ mControllerFocus++;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
+ if (mControllerFocus >= mRows)
+ mControllerFocus -= mRows;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
+ if (mControllerFocus + mRows < mItemCount)
+ mControllerFocus += mRows;
+ else if (mControllerFocus / mRows != (mItemCount - 1) / mRows)
+ mControllerFocus = mItemCount - 1;
+ break;
+ default:
+ return;
+ }
+
+ if (prevFocus != mControllerFocus)
+ updateControllerFocus(prevFocus, mControllerFocus);
+ else
+ updateControllerFocus(-1, mControllerFocus);
+ }
+
+ void ItemView::updateControllerFocus(int prevFocus, int newFocus)
+ {
+ MWBase::Environment::get().getWindowManager()->setCursorVisible(
+ !MWBase::Environment::get().getWindowManager()->getControllerTooltip());
+
+ if (!mItemCount)
+ return;
+
+ MyGUI::Widget* dragArea = mScrollView->getChildAt(0);
+
+ if (prevFocus >= 0 && prevFocus < mItemCount)
+ {
+ ItemWidget* prev = static_cast(dragArea->getChildAt(prevFocus));
+ if (prev)
+ prev->setControllerFocus(false);
+ }
+
+ if (mControllerActiveWindow && newFocus >= 0 && newFocus < mItemCount)
+ {
+ ItemWidget* focused = static_cast(dragArea->getChildAt(newFocus));
+ if (focused)
+ {
+ focused->setControllerFocus(true);
+
+ // Scroll the list to keep the active item in view
+ int column = newFocus / mRows;
+ if (column <= 3)
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ mScrollView->setViewOffset(MyGUI::IntPoint(-42 * (column - 3), 0));
+
+ if (MWBase::Environment::get().getWindowManager()->getControllerTooltip())
+ MWBase::Environment::get().getInputManager()->warpMouseToWidget(focused);
+ }
+ }
+ }
}
diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp
index aeed0a9113..6243618abc 100644
--- a/apps/openmw/mwgui/itemview.hpp
+++ b/apps/openmw/mwgui/itemview.hpp
@@ -33,6 +33,11 @@ namespace MWGui
void resetScrollBars();
+ void setActiveControllerWindow(bool active);
+ int getControllerFocus() { return mControllerFocus; }
+ int getItemCount() { return mItemCount; }
+ void onControllerButton(const unsigned char button);
+
private:
void initialiseOverride() override;
@@ -47,6 +52,12 @@ namespace MWGui
std::unique_ptr mModel;
MyGUI::ScrollView* mScrollView;
+
+ int mItemCount = 0;
+ int mRows;
+ int mControllerFocus = 0;
+ bool mControllerActiveWindow;
+ void updateControllerFocus(int prevFocus, int newFocus);
};
}
diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp
index 05fff2d40f..5e47577b27 100644
--- a/apps/openmw/mwgui/itemwidget.cpp
+++ b/apps/openmw/mwgui/itemwidget.cpp
@@ -58,6 +58,7 @@ namespace MWGui
: mItem(nullptr)
, mItemShadow(nullptr)
, mFrame(nullptr)
+ , mControllerBorder(nullptr)
, mText(nullptr)
{
}
@@ -82,10 +83,22 @@ namespace MWGui
assignWidget(mText, "Text");
if (mText)
mText->setNeedMouseFocus(false);
+ if (Settings::gui().mControllerMenus)
+ {
+ assignWidget(mControllerBorder, "ControllerBorder");
+ if (mControllerBorder)
+ mControllerBorder->setNeedMouseFocus(false);
+ }
Base::initialiseOverride();
}
+ void ItemWidget::setControllerFocus(bool focus)
+ {
+ if (mControllerBorder)
+ mControllerBorder->setVisible(focus);
+ }
+
void ItemWidget::setCount(int count)
{
if (!mText)
diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp
index 63837ae92f..1191a23342 100644
--- a/apps/openmw/mwgui/itemwidget.hpp
+++ b/apps/openmw/mwgui/itemwidget.hpp
@@ -40,12 +40,15 @@ namespace MWGui
void setIcon(const MWWorld::Ptr& ptr);
void setFrame(const std::string& frame, const MyGUI::IntCoord& coord);
+ void setControllerFocus(bool focus);
+
protected:
void initialiseOverride() override;
MyGUI::ImageBox* mItem;
MyGUI::ImageBox* mItemShadow;
MyGUI::ImageBox* mFrame;
+ MyGUI::ImageBox* mControllerBorder;
MyGUI::TextBox* mText;
std::string mCurrentIcon;
diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp
index 86b45b4863..c698fd84d6 100644
--- a/apps/openmw/mwgui/journalbooks.cpp
+++ b/apps/openmw/mwgui/journalbooks.cpp
@@ -156,6 +156,13 @@ namespace MWGui
return MWGui::BookTypesetter::Utf8Span(begin, begin + text.length());
}
+ int getCyrillicIndexPageCount()
+ {
+ // For small font size split alphabet to two columns (2x15 characers), for big font size split it to three
+ // colums (3x10 characters).
+ return Settings::gui().mFontSize < 18 ? 2 : 3;
+ }
+
typedef TypesetBook::Ptr book;
JournalBooks::JournalBooks(JournalViewModel::Ptr model, ToUTF8::FromType encoding)
@@ -169,7 +176,7 @@ namespace MWGui
{
BookTypesetter::Ptr typesetter = createTypesetter();
- BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* header = typesetter->createStyle({}, journalHeaderColour);
BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black);
typesetter->write(header, to_utf8_span("You have no journal entries!"));
@@ -184,7 +191,7 @@ namespace MWGui
{
BookTypesetter::Ptr typesetter = createTypesetter();
- BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* header = typesetter->createStyle({}, journalHeaderColour);
BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black);
mModel->visitJournalEntries({}, AddJournalEntry(typesetter, body, header, true));
@@ -196,7 +203,7 @@ namespace MWGui
{
BookTypesetter::Ptr typesetter = createTypesetter();
- BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* header = typesetter->createStyle({}, journalHeaderColour);
BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black);
mModel->visitTopicName(topicId, AddTopicName(typesetter, header));
@@ -212,7 +219,7 @@ namespace MWGui
{
BookTypesetter::Ptr typesetter = createTypesetter();
- BookTypesetter::Style* header = typesetter->createStyle({}, MyGUI::Colour(0.60f, 0.00f, 0.00f));
+ BookTypesetter::Style* header = typesetter->createStyle({}, journalHeaderColour);
BookTypesetter::Style* body = typesetter->createStyle({}, MyGUI::Colour::Black);
AddQuestName addName(typesetter, header);
@@ -277,13 +284,8 @@ namespace MWGui
// for small font size split alphabet to two columns (2x15 characers), for big font size split it to three
// colums (3x10 characters).
- int sectionBreak = 10;
- mIndexPagesCount = 3;
- if (Settings::gui().mFontSize < 18)
- {
- sectionBreak = 15;
- mIndexPagesCount = 2;
- }
+ mIndexPagesCount = getCyrillicIndexPageCount();
+ int sectionBreak = 30 / mIndexPagesCount;
unsigned char ch[3] = { 0xd0, 0x90, 0x00 }; // CYRILLIC CAPITAL A is a 0xd090 in UTF-8
diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp
index 792edcc070..3d55135d9c 100644
--- a/apps/openmw/mwgui/journalbooks.hpp
+++ b/apps/openmw/mwgui/journalbooks.hpp
@@ -9,6 +9,9 @@
namespace MWGui
{
MWGui::BookTypesetter::Utf8Span to_utf8_span(std::string_view text);
+ int getCyrillicIndexPageCount();
+
+ const MyGUI::Colour journalHeaderColour = MyGUI::Colour(0.60f, 0.00f, 0.00f);
struct JournalBooks
{
diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp
index 574c425d3e..adc05cf5b8 100644
--- a/apps/openmw/mwgui/journalwindow.cpp
+++ b/apps/openmw/mwgui/journalwindow.cpp
@@ -218,6 +218,16 @@ namespace
}
}
+ // Latin = 26 (13 + 13)
+ mIndexRowCount = 13;
+ bool isRussian = (mEncoding == ToUTF8::WINDOWS_1251);
+ if (isRussian) // Cyrillic is either (10 + 10 + 10) or (15 + 15)
+ mIndexRowCount = MWGui::getCyrillicIndexPageCount();
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mX = "#{OMWEngine:JournalQuests}";
+ mControllerButtons.mY = "#{sTopics}";
+
mQuestMode = false;
mAllQuests = false;
mOptionsMode = false;
@@ -248,6 +258,9 @@ namespace
}
updateShowingPages();
+ if (Settings::gui().mControllerMenus)
+ setControllerFocusedQuest(0);
+
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(getWidget(CloseBTN));
}
@@ -275,6 +288,8 @@ namespace
updateShowingPages();
updateCloseJournalButton();
+
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void setOptionsMode()
@@ -307,6 +322,8 @@ namespace
notifyQuests(getWidget(QuestsList));
else
notifyTopics(getWidget(TopicsList));
+
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void pushBook(Book& book, unsigned int page)
@@ -338,6 +355,7 @@ namespace
{
setVisible(CloseBTN, mStates.size() < 2);
setVisible(JournalBTN, mStates.size() >= 2);
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void updateShowingPages()
@@ -380,6 +398,8 @@ namespace
setText(PageOneNum, page + 1);
setText(PageTwoNum, page + 2);
+
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void notifyKeyPress(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character)
@@ -407,6 +427,7 @@ namespace
mTopicsMode = false;
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page"));
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void notifyTopicSelected(const std::string& topicIdString, int id)
@@ -439,6 +460,7 @@ namespace
mOptionsMode = false;
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page"));
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void notifyOptions(MyGUI::Widget* _sender)
@@ -459,6 +481,9 @@ namespace
getPage(LeftTopicIndex)->showPage(mTopicIndexBook, 0);
getPage(RightTopicIndex)->showPage(mTopicIndexBook, 1);
}
+
+ if (Settings::gui().mControllerMenus)
+ setIndexControllerFocus(true);
}
void notifyJournal(MyGUI::Widget* _sender)
@@ -467,6 +492,22 @@ namespace
popBook();
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page"));
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
+ }
+
+ void addControllerButtons(Gui::MWList* _list, size_t _selectedIndex)
+ {
+ mButtons.clear();
+ for (size_t i = 0; i < _list->getItemCount(); i++)
+ {
+ MyGUI::Button* listItem = _list->getItemWidget(_list->getItemNameAt(i));
+ if (listItem)
+ {
+ listItem->setTextColour(
+ mButtons.size() == _selectedIndex ? MWGui::journalHeaderColour : MyGUI::Colour::Black);
+ mButtons.push_back(listItem);
+ }
+ }
}
void notifyIndexLinkClicked(MWGui::TypesetBook::InteractiveId index)
@@ -487,7 +528,14 @@ namespace
list->adjustSize();
+ if (Settings::gui().mControllerMenus)
+ {
+ setControllerFocusedQuest(0);
+ addControllerButtons(list, mSelectedQuest);
+ }
+
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page"));
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void notifyTopics(MyGUI::Widget* _sender)
@@ -503,6 +551,7 @@ namespace
setVisible(ShowActiveBTN, false);
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page"));
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
struct AddNamesToList
@@ -554,6 +603,12 @@ namespace
list->sort();
list->adjustSize();
+ if (Settings::gui().mControllerMenus)
+ {
+ addControllerButtons(list, mSelectedQuest);
+ setControllerFocusedQuest(MWGui::wrap(mSelectedQuest, mButtons.size()));
+ }
+
if (mAllQuests)
{
SetNamesInactive setInactive(list);
@@ -561,6 +616,7 @@ namespace
}
MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("book page"));
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void notifyShowAll(MyGUI::Widget* _sender)
@@ -639,6 +695,240 @@ namespace
}
}
}
+
+ MWGui::ControllerButtons* getControllerButtons() override
+ {
+ mControllerButtons.mB = mOptionsMode || mStates.size() > 1 ? "#{sBack}" : "#{Interface:Close}";
+ mControllerButtons.mL1 = mOptionsMode ? "" : "#{sPrev}";
+ mControllerButtons.mR1 = mOptionsMode ? "" : "#{sNext}";
+ mControllerButtons.mR3 = mOptionsMode && mQuestMode ? "#{OMWEngine:JournalShowAll}" : "";
+ return &mControllerButtons;
+ }
+
+ void setIndexControllerFocus(bool focused)
+ {
+ int col = mSelectedIndex / mIndexRowCount;
+ int row = mSelectedIndex % mIndexRowCount;
+ mTopicIndexBook->setColour(col, row, 0, focused ? MWGui::journalHeaderColour : MyGUI::Colour::Black);
+ }
+
+ void moveSelectedIndex(int offset)
+ {
+ setIndexControllerFocus(false);
+
+ int numChars = mEncoding == ToUTF8::WINDOWS_1251 ? 30 : 26;
+ int col = mSelectedIndex / mIndexRowCount;
+
+ if (offset == -1) // Up
+ {
+ if (mSelectedIndex % mIndexRowCount == 0)
+ mSelectedIndex = (col * mIndexRowCount) + mIndexRowCount - 1;
+ else
+ mSelectedIndex--;
+ }
+ else if (offset == 1) // Down
+ {
+ if (mSelectedIndex % mIndexRowCount == mIndexRowCount - 1)
+ mSelectedIndex = col * mIndexRowCount;
+ else
+ mSelectedIndex++;
+ }
+ else
+ {
+ // mSelectedIndex is unsigned, so we have to be careful with our math.
+ if (offset < 0)
+ offset += numChars;
+
+ mSelectedIndex = (mSelectedIndex + offset) % numChars;
+ }
+
+ setIndexControllerFocus(true);
+ setText(PageOneNum, 1); // Redraw the list
+ }
+
+ bool optionsModeButtonHandler(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A) // A: Mouse click or Select
+ {
+ if (mQuestMode)
+ {
+ // Choose a quest
+ Gui::MWList* list = getWidget(QuestsList);
+ if (mSelectedQuest < list->getItemCount())
+ notifyQuestClicked(list->getItemNameAt(mSelectedQuest), 0);
+ }
+ else if (mTopicsMode)
+ {
+ // Choose a topic
+ Gui::MWList* list = getWidget(TopicsList);
+ if (mSelectedQuest < list->getItemCount())
+ notifyTopicSelected(list->getItemNameAt(mSelectedQuest), 0);
+ }
+ else
+ {
+ // Choose an index. Cyrillic capital A is a 0xd090 in UTF-8.
+ // Words can not be started with characters 26 or 28.
+ int russianOffset = 0xd090;
+ if (mSelectedIndex >= 26)
+ russianOffset++;
+ if (mSelectedIndex >= 27)
+ russianOffset++; // 27, not 28, because of skipping char 26
+ bool isRussian = (mEncoding == ToUTF8::WINDOWS_1251);
+ notifyIndexLinkClicked(isRussian ? mSelectedIndex + russianOffset : mSelectedIndex + 'A');
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B) // B: Back
+ {
+ // Hide the options overlay
+ notifyCancel(getWidget(CancelBTN));
+ mQuestMode = false;
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X) // X: Quests
+ {
+ if (mQuestMode)
+ {
+ // Hide the quest overlay if visible
+ notifyCancel(getWidget(CancelBTN));
+ mQuestMode = false;
+ }
+ else
+ {
+ // Show the quest overlay if viewing the topics list
+ notifyQuests(getWidget(QuestsBTN));
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y) // Y: Topics
+ {
+ if (!mQuestMode)
+ {
+ // Hide the topics overlay if visible
+ notifyCancel(getWidget(CancelBTN));
+ }
+ else
+ {
+ // Show the topics overlay if viewing the quest list
+ notifyTopics(getWidget(TopicsBTN));
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK && mQuestMode) // R3: Show All/Some
+ {
+ if (mAllQuests)
+ notifyShowActive(getWidget(ShowActiveBTN));
+ else
+ notifyShowAll(getWidget(ShowAllBTN));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ if (mQuestMode || mTopicsMode)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+
+ // Scroll through the list of quests or topics
+ setControllerFocusedQuest(MWGui::wrap(mSelectedQuest - 1, mButtons.size()));
+ }
+ else
+ moveSelectedIndex(-1);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (mQuestMode || mTopicsMode)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+
+ // Scroll through the list of quests or topics
+ setControllerFocusedQuest(MWGui::wrap(mSelectedQuest + 1, mButtons.size()));
+ }
+ else
+ moveSelectedIndex(1);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && !mQuestMode && !mTopicsMode)
+ moveSelectedIndex(-mIndexRowCount);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && !mQuestMode && !mTopicsMode)
+ moveSelectedIndex(mIndexRowCount);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER && (mQuestMode || mTopicsMode))
+ {
+ // Scroll up 5 items in the list of quests or topics
+ setControllerFocusedQuest(std::max(static_cast(mSelectedQuest) - 5, 0));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER && (mQuestMode || mTopicsMode))
+ {
+ // Scroll down 5 items in the list of quests or topics
+ setControllerFocusedQuest(std::min(mSelectedQuest + 5, mButtons.size() - 1));
+ }
+
+ return true;
+ }
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override
+ {
+ // If the topics or quest list is open, it should handle the buttons.
+ if (mOptionsMode)
+ return optionsModeButtonHandler(arg);
+
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ return false;
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B) // B: Back
+ {
+ if (mStates.size() > 1)
+ {
+ // Pop the current book. If in quest mode, reopen the quest list.
+ notifyJournal(getWidget(JournalBTN));
+ if (mQuestMode)
+ {
+ notifyOptions(getWidget(OptionsBTN));
+ notifyQuests(getWidget(QuestsBTN));
+ }
+ }
+ else
+ {
+ // Close the journal window
+ notifyClose(getWidget(CloseBTN));
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X) // X: Quests
+ {
+ // Show the quest overlay
+ notifyOptions(getWidget(OptionsBTN));
+ if (!mQuestMode)
+ notifyQuests(getWidget(QuestsBTN));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y) // Y: Topics
+ {
+ // Show the topics overlay
+ notifyOptions(getWidget(OptionsBTN));
+ if (mQuestMode)
+ notifyTopics(getWidget(TopicsBTN));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT || arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ notifyPrevPage(getWidget(PrevPageBTN));
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT
+ || arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ notifyNextPage(getWidget(NextPageBTN));
+
+ return true;
+ }
+
+ void setControllerFocusedQuest(size_t index)
+ {
+ size_t listSize = mButtons.size();
+ if (mSelectedQuest < listSize)
+ mButtons[mSelectedQuest]->setTextColour(MyGUI::Colour::Black);
+
+ mSelectedQuest = index;
+ if (mSelectedQuest < listSize)
+ {
+ mButtons[mSelectedQuest]->setTextColour(MWGui::journalHeaderColour);
+
+ // Scroll the list to keep the active item in view
+ Gui::MWList* list = getWidget(mQuestMode ? QuestsList : TopicsList);
+ int offset = 0;
+ for (int i = 4; i < static_cast(mSelectedQuest); i++)
+ offset += mButtons[i]->getHeight();
+ list->setViewOffset(-offset);
+ }
+ }
};
}
diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp
index f0f394156c..6122bf70c7 100644
--- a/apps/openmw/mwgui/journalwindow.hpp
+++ b/apps/openmw/mwgui/journalwindow.hpp
@@ -31,6 +31,16 @@ namespace MWGui
void setVisible(bool newValue) override = 0;
std::string_view getWindowIdForLua() const override { return "Journal"; }
+
+ size_t mIndexRowCount;
+
+ std::vector mButtons;
+ size_t mSelectedQuest = 0;
+ size_t mSelectedIndex = 0;
+ void moveSelectedIndex(int offset);
+ void setIndexControllerFocus(bool focused);
+ void setControllerFocusedQuest(size_t index);
+ bool optionsModeButtonHandler(const SDL_ControllerButtonEvent& arg);
};
}
diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp
index 87f2db55a5..708fc7b02f 100644
--- a/apps/openmw/mwgui/levelupdialog.cpp
+++ b/apps/openmw/mwgui/levelupdialog.cpp
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include "../mwbase/environment.hpp"
@@ -48,13 +49,12 @@ namespace MWGui
{
const auto& store = MWBase::Environment::get().getESMStore()->get();
- const size_t perCol
- = static_cast(std::ceil(store.getSize() / static_cast(std::size(sColumnOffsets))));
+ mPerCol = static_cast(std::ceil(store.getSize() / static_cast(std::size(sColumnOffsets))));
size_t i = 0;
for (const ESM::Attribute& attribute : store)
{
- const int offset = sColumnOffsets[i / perCol];
- const int row = static_cast(i % perCol);
+ const int offset = sColumnOffsets[i / mPerCol];
+ const int row = static_cast(i % mPerCol);
Widgets widgets;
widgets.mMultiplier = mAssignWidget->createWidget(
"SandTextVCenter", { offset, 20 * row, 100, 20 }, MyGUI::Align::Default);
@@ -72,12 +72,13 @@ namespace MWGui
widgets.mButton->setCaption(attribute.mName);
widgets.mValue = hbox->createWidget("SandText", {}, MyGUI::Align::Default);
mAttributeWidgets.emplace(attribute.mId, widgets);
+ mAttributeButtons.emplace_back(widgets.mButton);
++i;
}
mAssignWidget->setVisibleVScroll(false);
mAssignWidget->setCanvasSize(MyGUI::IntSize(
- mAssignWidget->getWidth(), std::max(mAssignWidget->getHeight(), static_cast(20 * perCol))));
+ mAssignWidget->getWidth(), std::max(mAssignWidget->getHeight(), static_cast(20 * mPerCol))));
mAssignWidget->setVisibleVScroll(true);
mAssignWidget->setViewOffset(MyGUI::IntPoint());
}
@@ -90,6 +91,15 @@ namespace MWGui
mCoins.push_back(image);
}
+ if (Settings::gui().mControllerMenus)
+ {
+ mDisableGamepadCursor = true;
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mX = "#{sDone}";
+ mOkButton->setCaption(
+ MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sDone", {})));
+ }
+
center();
}
@@ -217,6 +227,13 @@ namespace MWGui
center();
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ for (size_t i = 0; i < mAttributeButtons.size(); i++)
+ setControllerFocus(mAttributeButtons, i, i == 0);
+ }
+
// Play LevelUp Music
MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Normal);
}
@@ -363,4 +380,44 @@ namespace MWGui
return ret;
}
+
+ bool LevelupDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus < mAttributeButtons.size())
+ onAttributeClicked(mAttributeButtons[mControllerFocus]);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Item Gold Up"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkButtonClicked(mOkButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ setControllerFocus(mAttributeButtons, mControllerFocus, false);
+ if (mControllerFocus % mPerCol == 0)
+ mControllerFocus += mPerCol - 1;
+ else
+ mControllerFocus--;
+ setControllerFocus(mAttributeButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ setControllerFocus(mAttributeButtons, mControllerFocus, false);
+ if (mControllerFocus % mPerCol == mPerCol - 1)
+ mControllerFocus -= mPerCol - 1;
+ else
+ mControllerFocus++;
+ setControllerFocus(mAttributeButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT || arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ setControllerFocus(mAttributeButtons, mControllerFocus, false);
+ mControllerFocus = (mControllerFocus + mPerCol) % mAttributeButtons.size();
+ setControllerFocus(mAttributeButtons, mControllerFocus, true);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/levelupdialog.hpp b/apps/openmw/mwgui/levelupdialog.hpp
index 486390679b..70bd2839f2 100644
--- a/apps/openmw/mwgui/levelupdialog.hpp
+++ b/apps/openmw/mwgui/levelupdialog.hpp
@@ -37,6 +37,7 @@ namespace MWGui
std::vector mSpentAttributes;
+ size_t mPerCol;
unsigned int mCoinCount;
void onOkButtonClicked(MyGUI::Widget* sender);
@@ -49,6 +50,10 @@ namespace MWGui
std::string_view getLevelupClassImage(
const int combatIncreases, const int magicIncreases, const int stealthIncreases);
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ std::vector mAttributeButtons;
+ size_t mControllerFocus;
};
}
diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp
index 1b3619bd9f..d9d5b5c33b 100644
--- a/apps/openmw/mwgui/mainmenu.cpp
+++ b/apps/openmw/mwgui/mainmenu.cpp
@@ -1,6 +1,7 @@
#include "mainmenu.hpp"
#include
+#include
#include
#include
@@ -105,6 +106,7 @@ namespace MWGui
constexpr VFS::Path::NormalizedView menuBackgroundVideo("video/menu_background.bik");
mHasAnimatedMenu = mVFS->exists(menuBackgroundVideo);
+ mDisableGamepadCursor = Settings::gui().mControllerMenus;
updateMenu();
}
@@ -163,9 +165,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 +208,32 @@ namespace MWGui
}
}
+ bool MainMenu::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Space, 0, false);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B || arg.button == SDL_CONTROLLER_BUTTON_START)
+ {
+ if (mButtons["return"]->getVisible())
+ onButtonClicked(mButtons["return"]);
+ else
+ MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Escape, 0, false);
+ }
+ else if (arg.button == 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);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Tab, 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/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp
index 51a765442a..5543caf09f 100644
--- a/apps/openmw/mwgui/mapwindow.cpp
+++ b/apps/openmw/mwgui/mapwindow.cpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
@@ -830,6 +831,14 @@ namespace MWGui
mGlobalMap->setVisible(global);
mLocalMap->setVisible(!global);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mB = "#{sBack}";
+ mControllerButtons.mX = global ? "#{sLocal}" : "#{sWorld}";
+ mControllerButtons.mY = "#{sCenter}";
+ mControllerButtons.mDpad = Settings::map().mAllowZooming ? "" : "#{sMove}";
+ }
}
void MapWindow::onNoteEditOk()
@@ -1018,7 +1027,20 @@ namespace MWGui
void MapWindow::setVisible(bool visible)
{
WindowBase::setVisible(visible);
- mButton->setVisible(visible && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_None);
+ MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode();
+ mButton->setVisible(visible && mode != MWGui::GM_None);
+
+ if (Settings::gui().mControllerMenus && mode == MWGui::GM_None && pinned() && visible)
+ {
+ // Restore the window to pinned size.
+ MyGUI::Window* window = mMainWidget->castType();
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ const float x = Settings::windows().mMapX * viewSize.width;
+ const float y = Settings::windows().mMapY * viewSize.height;
+ const float w = Settings::windows().mMapW * viewSize.width;
+ const float h = Settings::windows().mMapH * viewSize.height;
+ window->setCoord(x, y, w, h);
+ }
}
void MapWindow::renderGlobalMap()
@@ -1206,6 +1228,8 @@ namespace MWGui
mLocalMap->setVisible(!global);
mButton->setCaptionWithReplacing(global ? "#{sLocal}" : "#{sWorld}");
+ mControllerButtons.mX = global ? "#{sLocal}" : "#{sWorld}";
+ MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay();
}
void MapWindow::onPinToggled()
@@ -1217,7 +1241,9 @@ namespace MWGui
void MapWindow::onTitleDoubleClicked()
{
- if (MyGUI::InputManager::getInstance().isShiftPressed())
+ if (Settings::gui().mControllerMenus)
+ return;
+ else if (MyGUI::InputManager::getInstance().isShiftPressed())
MWBase::Environment::get().getWindowManager()->toggleMaximized(this);
else if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map);
@@ -1367,6 +1393,73 @@ namespace MWGui
mGlobalMapRender->asyncWritePng();
}
+ bool MapWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onWorldButtonClicked(mButton);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y)
+ {
+ centerView();
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ shiftMap(0, 100);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ shiftMap(0, -100);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ shiftMap(100, 0);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ shiftMap(-100, 0);
+
+ return true;
+ }
+
+ void MapWindow::shiftMap(int dx, int dy)
+ {
+ if (dx == 0 && dy == 0)
+ return;
+
+ if (!Settings::map().mGlobal)
+ {
+ mNeedDoorMarkersUpdate = true;
+ mLocalMap->setViewOffset(
+ MyGUI::IntPoint(mLocalMap->getViewOffset().left + dx, mLocalMap->getViewOffset().top + dy));
+ }
+ else
+ {
+ mGlobalMap->setViewOffset(
+ MyGUI::IntPoint(mGlobalMap->getViewOffset().left + dx, mGlobalMap->getViewOffset().top + dy));
+ }
+ }
+
+ void MapWindow::setActiveControllerWindow(bool active)
+ {
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+ if (winMgr->getMode() == MWGui::GM_Inventory)
+ {
+ // Fill the screen, or limit to a certain size on large screens. Size chosen to
+ // show the entire local map without scrolling.
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ MyGUI::IntSize canvasSize = mLocalMap->getCanvasSize();
+ MyGUI::IntSize borderSize = mMainWidget->getSize() - mMainWidget->getClientWidget()->getSize();
+
+ int width = std::min(viewSize.width, canvasSize.width + borderSize.width);
+ int height = std::min(winMgr->getControllerMenuHeight(), canvasSize.height + borderSize.height);
+ int x = (viewSize.width - width) / 2;
+ int y = (viewSize.height - height) / 2;
+
+ MyGUI::Window* window = mMainWidget->castType();
+ window->setCoord(x, active ? y : viewSize.height + 1, width, height);
+ }
+
+ WindowBase::setActiveControllerWindow(active);
+ }
+
// -------------------------------------------------------------------
EditNoteDialog::EditNoteDialog()
@@ -1380,6 +1473,12 @@ namespace MWGui
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onCancelButtonClicked);
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onOkButtonClicked);
mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &EditNoteDialog::onDeleteButtonClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mA = "#{Interface:OK}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
}
void EditNoteDialog::showDeleteButton(bool show)
@@ -1407,6 +1506,13 @@ namespace MWGui
WindowModal::onOpen();
center();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = getDeleteButtonShown() ? 1 : 0;
+ mOkButton->setStateSelected(true);
+ mCancelButton->setStateSelected(false);
+ }
}
void EditNoteDialog::onCancelButtonClicked(MyGUI::Widget* sender)
@@ -1424,6 +1530,78 @@ namespace MWGui
eventDeleteClicked();
}
+ ControllerButtons* EditNoteDialog::getControllerButtons()
+ {
+ mControllerButtons.mX = getDeleteButtonShown() ? "#{sDelete}" : "";
+ return &mControllerButtons;
+ }
+
+ bool EditNoteDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (getDeleteButtonShown())
+ {
+ if (mControllerFocus == 0)
+ onDeleteButtonClicked(mDeleteButton);
+ else if (mControllerFocus == 1)
+ onOkButtonClicked(mOkButton);
+ else
+ onCancelButtonClicked(mCancelButton);
+ }
+ else
+ {
+ if (mControllerFocus == 0)
+ onOkButtonClicked(mOkButton);
+ else
+ onCancelButtonClicked(mCancelButton);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelButtonClicked(mCancelButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ if (getDeleteButtonShown())
+ onDeleteButtonClicked(mDeleteButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ {
+ if (getDeleteButtonShown())
+ {
+ mControllerFocus = wrap(mControllerFocus - 1, 3);
+ mDeleteButton->setStateSelected(mControllerFocus == 0);
+ mOkButton->setStateSelected(mControllerFocus == 1);
+ mCancelButton->setStateSelected(mControllerFocus == 2);
+ }
+ else
+ {
+ mControllerFocus = 0;
+ mOkButton->setStateSelected(mControllerFocus == 0);
+ mCancelButton->setStateSelected(mControllerFocus == 1);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ if (getDeleteButtonShown())
+ {
+ mControllerFocus = wrap(mControllerFocus + 1, 3);
+ mDeleteButton->setStateSelected(mControllerFocus == 0);
+ mOkButton->setStateSelected(mControllerFocus == 1);
+ mCancelButton->setStateSelected(mControllerFocus == 2);
+ }
+ else
+ {
+ mControllerFocus = 1;
+ mOkButton->setStateSelected(mControllerFocus == 0);
+ mCancelButton->setStateSelected(mControllerFocus == 1);
+ }
+ }
+
+ return true;
+ }
+
bool LocalMapBase::MarkerUserData::isPositionExplored() const
{
if (!mLocalMapRender)
diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp
index ed070c5407..9a474e8170 100644
--- a/apps/openmw/mwgui/mapwindow.hpp
+++ b/apps/openmw/mwgui/mapwindow.hpp
@@ -212,6 +212,8 @@ namespace MWGui
EventHandle_Void eventDeleteClicked;
EventHandle_Void eventOkClicked;
+ ControllerButtons* getControllerButtons() override;
+
private:
void onCancelButtonClicked(MyGUI::Widget* sender);
void onOkButtonClicked(MyGUI::Widget* sender);
@@ -221,6 +223,9 @@ namespace MWGui
MyGUI::Button* mOkButton;
MyGUI::Button* mCancelButton;
MyGUI::Button* mDeleteButton;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ int mControllerFocus;
};
class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop
@@ -265,6 +270,10 @@ namespace MWGui
std::string_view getWindowIdForLua() const override { return "Map"; }
+ protected:
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void setActiveControllerWindow(bool active) override;
+
private:
void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
@@ -283,6 +292,7 @@ namespace MWGui
void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y);
float getMarkerSize(size_t agregatedWeight) const;
void resizeGlobalMap();
+ void shiftMap(int dx, int dy);
void worldPosToGlobalMapImageSpace(float x, float z, float& imageX, float& imageY) const;
MyGUI::IntCoord createMarkerCoords(float x, float y, float agregatedWeight) const;
MyGUI::Widget* createMarker(const std::string& name, float x, float y, float agregatedWeight);
diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp
index 54f9ae4187..87cda0b4db 100644
--- a/apps/openmw/mwgui/merchantrepair.cpp
+++ b/apps/openmw/mwgui/merchantrepair.cpp
@@ -28,6 +28,13 @@ namespace MWGui
getWidget(mGoldLabel, "PlayerGold");
mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mDisableGamepadCursor = true;
+ mControllerButtons.mA = "#{sRepair}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
}
void MerchantRepair::setPtr(const MWWorld::Ptr& actor)
@@ -38,6 +45,7 @@ namespace MWGui
while (mList->getChildCount())
MyGUI::Gui::getInstance().destroyWidget(mList->getChildAt(0));
+ mButtons.clear();
const int lineHeight = Settings::gui().mFontSize + 2;
int currentY = 0;
@@ -101,6 +109,15 @@ namespace MWGui
button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel);
button->setUserString("ToolTipType", "ItemPtr");
button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick);
+ if (price <= playerGold)
+ mButtons.emplace_back(std::make_pair(button, mButtons.size()));
+ }
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ if (mButtons.size() > 0)
+ mButtons[0].first->setStateSelected(true);
}
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the
@@ -157,4 +174,49 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair);
}
+ bool MerchantRepair::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus < mButtons.size())
+ onRepairButtonClick(mButtons[mControllerFocus].first);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onOkButtonClick(mOkButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+
+ mButtons[mControllerFocus].first->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus - 1, mButtons.size());
+ mButtons[mControllerFocus].first->setStateSelected(true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+
+ mButtons[mControllerFocus].first->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus + 1, mButtons.size());
+ mButtons[mControllerFocus].first->setStateSelected(true);
+ }
+
+ // Scroll the list to keep the active item in view
+ if (mControllerFocus < mButtons.size())
+ {
+ size_t line = mButtons[mControllerFocus].second;
+ if (line <= 5)
+ mList->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ {
+ const int lineHeight = Settings::gui().mFontSize + 2;
+ mList->setViewOffset(MyGUI::IntPoint(0, -lineHeight * (line - 5)));
+ }
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp
index ffe5b86bdb..28fb2a7c3c 100644
--- a/apps/openmw/mwgui/merchantrepair.hpp
+++ b/apps/openmw/mwgui/merchantrepair.hpp
@@ -22,13 +22,18 @@ namespace MWGui
MyGUI::ScrollView* mList;
MyGUI::Button* mOkButton;
MyGUI::TextBox* mGoldLabel;
+ /// List of enabled/repairable items and their index in the full list.
+ std::vector> mButtons;
MWWorld::Ptr mActor;
+ size_t mControllerFocus;
+
protected:
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
void onRepairButtonClick(MyGUI::Widget* sender);
void onOkButtonClick(MyGUI::Widget* sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp
index 1d6e1511c4..d705d66bb6 100644
--- a/apps/openmw/mwgui/messagebox.cpp
+++ b/apps/openmw/mwgui/messagebox.cpp
@@ -8,6 +8,7 @@
#include
#include
+#include
#include "../mwbase/environment.hpp"
#include "../mwbase/inputmanager.hpp"
@@ -217,7 +218,7 @@ namespace MWGui
}
InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message,
- const std::vector& buttons, bool immediate, int defaultFocus)
+ const std::vector& buttons, bool immediate, size_t defaultFocus)
: WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode()
? "openmw_interactive_messagebox_notransp.layout"
: "openmw_interactive_messagebox.layout")
@@ -225,6 +226,7 @@ namespace MWGui
, mButtonPressed(-1)
, mDefaultFocus(defaultFocus)
, mImmediate(immediate)
+ , mControllerFocus(0)
{
int textPadding = 10; // padding between text-widget and main-widget
int textButtonPadding = 10; // padding between the text-widget und the button-widget
@@ -280,6 +282,22 @@ namespace MWGui
}
}
+ if (Settings::gui().mControllerMenus)
+ {
+ mDisableGamepadCursor = true;
+ mControllerButtons.mA = "#{Interface:OK}";
+
+ // If we have more than one button, we need to set the focus to the first one.
+ if (mButtons.size() > 1)
+ {
+ mControllerFocus = 0;
+ if (mDefaultFocus < mButtons.size())
+ mControllerFocus = mDefaultFocus;
+ for (size_t i = 0; i < mButtons.size(); ++i)
+ mButtons[i]->setStateSelected(i == mControllerFocus);
+ }
+ }
+
MyGUI::IntSize mainWidgetSize;
if (buttonsWidth < textSize.width)
{
@@ -380,7 +398,7 @@ namespace MWGui
MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus()
{
- if (mDefaultFocus >= 0 && mDefaultFocus < static_cast(mButtons.size()))
+ if (mDefaultFocus < mButtons.size())
return mButtons[mDefaultFocus];
auto& languageManager = MyGUI::LanguageManager::getInstance();
std::vector keywords{ languageManager.replaceTags("#{sOk}"),
@@ -431,4 +449,45 @@ namespace MWGui
return mButtonPressed;
}
+ bool InteractiveMessageBox::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (!mButtons.empty())
+ {
+ if (mControllerFocus >= mButtons.size())
+ mControllerFocus = mButtons.size() - 1;
+ buttonActivated(mButtons[mControllerFocus]);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ if (mButtons.size() == 1)
+ buttonActivated(mButtons[0]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP || arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+ if (mButtons.size() == 2 && mControllerFocus == 0)
+ return true;
+
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus - 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN || arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ if (mButtons.size() <= 1)
+ return true;
+ if (mButtons.size() == 2 && mControllerFocus == 1)
+ return true;
+
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus + 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp
index feb717e0ad..98eb69cc69 100644
--- a/apps/openmw/mwgui/messagebox.hpp
+++ b/apps/openmw/mwgui/messagebox.hpp
@@ -93,7 +93,7 @@ namespace MWGui
{
public:
InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message,
- const std::vector& buttons, bool immediate, int defaultFocus);
+ const std::vector& buttons, bool immediate, size_t defaultFocus);
void mousePressed(MyGUI::Widget* _widget);
int readPressedButton();
@@ -103,6 +103,8 @@ namespace MWGui
bool mMarkedToDelete;
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+
private:
void buttonActivated(MyGUI::Widget* _widget);
@@ -112,8 +114,9 @@ namespace MWGui
std::vector mButtons;
int mButtonPressed;
- int mDefaultFocus;
+ size_t mDefaultFocus;
bool mImmediate;
+ size_t mControllerFocus;
};
}
diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp
index 3c62400e0d..782949b99a 100644
--- a/apps/openmw/mwgui/quickkeysmenu.cpp
+++ b/apps/openmw/mwgui/quickkeysmenu.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
@@ -39,7 +40,7 @@ namespace MWGui
, mKey(std::vector(10))
, mSelected(nullptr)
, mActivated(nullptr)
-
+ , mControllerFocus(0)
{
getWidget(mOkButton, "OKButton");
getWidget(mInstructionLabel, "InstructionLabel");
@@ -58,6 +59,12 @@ namespace MWGui
unassign(&mKey[i]);
}
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:OK}";
+ }
}
void QuickKeysMenu::clear()
@@ -108,6 +115,13 @@ namespace MWGui
{
validate(index);
}
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ for (size_t i = 0; i < mKey.size(); i++)
+ mKey[i].button->setControllerFocus(i == mControllerFocus);
+ }
}
void QuickKeysMenu::onClose()
@@ -454,11 +468,45 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->updateSpellWindow();
}
+ bool QuickKeysMenu::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ onQuickKeyButtonClicked(mKey[mControllerFocus].button);
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onOkButtonClicked(mOkButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP || arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ mControllerFocus = (mControllerFocus + 5) % 10;
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ {
+ if (mControllerFocus == 0)
+ mControllerFocus = 4;
+ else if (mControllerFocus == 5)
+ mControllerFocus = 9;
+ else
+ mControllerFocus--;
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ if (mControllerFocus == 4)
+ mControllerFocus = 0;
+ else if (mControllerFocus == 9)
+ mControllerFocus = 5;
+ else
+ mControllerFocus++;
+ }
+
+ for (size_t i = 0; i < mKey.size(); i++)
+ mKey[i].button->setControllerFocus(i == mControllerFocus);
+
+ return true;
+ }
+
// ---------------------------------------------------------------------------------------------------------
QuickKeysMenuAssign::QuickKeysMenuAssign(QuickKeysMenu* parent)
: WindowModal("openmw_quickkeys_menu_assign.layout")
, mParent(parent)
+ , mControllerFocus(0)
{
getWidget(mLabel, "Label");
getWidget(mItemButton, "ItemButton");
@@ -489,9 +537,45 @@ namespace MWGui
mCancelButton->setCoord((maxWidth - mCancelButton->getTextSize().width - 24) / 2 + 8, mCancelButton->getTop(),
mCancelButton->getTextSize().width + 24, mCancelButton->getHeight());
+ if (Settings::gui().mControllerMenus)
+ {
+ mDisableGamepadCursor = true;
+ mItemButton->setStateSelected(true);
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
+
center();
}
+ bool QuickKeysMenuAssign::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mControllerFocus == 0)
+ mParent->onItemButtonClicked(mItemButton);
+ else if (mControllerFocus == 1)
+ mParent->onMagicButtonClicked(mMagicButton);
+ else if (mControllerFocus == 2)
+ mParent->onUnassignButtonClicked(mUnassignButton);
+ else if (mControllerFocus == 3)
+ mParent->onCancelButtonClicked(mCancelButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ mParent->onCancelButtonClicked(mCancelButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ mControllerFocus = wrap(mControllerFocus - 1, 4);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ mControllerFocus = wrap(mControllerFocus + 1, 4);
+
+ mItemButton->setStateSelected(mControllerFocus == 0);
+ mMagicButton->setStateSelected(mControllerFocus == 1);
+ mUnassignButton->setStateSelected(mControllerFocus == 2);
+ mCancelButton->setStateSelected(mControllerFocus == 3);
+
+ return true;
+ }
+
void QuickKeysMenu::write(ESM::ESMWriter& writer)
{
writer.startRecord(ESM::REC_KEYS);
@@ -601,6 +685,12 @@ namespace MWGui
mMagicList->setHighlightSelected(false);
mMagicList->eventSpellClicked += MyGUI::newDelegate(this, &MagicSelectionDialog::onModelIndexSelected);
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ }
+
center();
}
@@ -632,4 +722,13 @@ namespace MWGui
mParent->onAssignMagic(spell.mId);
}
+ bool MagicSelectionDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancelButtonClicked(mCancelButton);
+ else
+ mMagicList->onControllerButton(arg.button);
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp
index a43cce50b4..11648cdb40 100644
--- a/apps/openmw/mwgui/quickkeysmenu.hpp
+++ b/apps/openmw/mwgui/quickkeysmenu.hpp
@@ -73,6 +73,9 @@ namespace MWGui
inline void validate(int index);
void unassign(keyData* key);
void assignItem(MWWorld::Ptr item);
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ size_t mControllerFocus;
};
class QuickKeysMenuAssign : public WindowModal
@@ -88,6 +91,9 @@ namespace MWGui
MyGUI::Button* mCancelButton;
QuickKeysMenu* mParent;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ int mControllerFocus;
};
class MagicSelectionDialog : public WindowModal
@@ -106,6 +112,9 @@ namespace MWGui
void onCancelButtonClicked(MyGUI::Widget* sender);
void onModelIndexSelected(SpellModel::ModelIndex index);
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ int mControllerFocus;
};
}
diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp
index c7de8f4125..193c409ab7 100644
--- a/apps/openmw/mwgui/race.cpp
+++ b/apps/openmw/mwgui/race.cpp
@@ -23,16 +23,6 @@
namespace
{
- int wrap(int index, int max)
- {
- if (index < 0)
- return max - 1;
- else if (index >= max)
- return 0;
- else
- return index;
- }
-
bool sortRaces(const std::pair& left, const std::pair& right)
{
return left.second.compare(right.second) < 0;
@@ -108,15 +98,23 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu7", "Specials"));
getWidget(mSpellPowerList, "SpellPowerList");
- MyGUI::Button* backButton;
- getWidget(backButton, "BackButton");
- backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked);
+ getWidget(mBackButton, "BackButton");
+ mBackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked);
- MyGUI::Button* okButton;
- getWidget(okButton, "OKButton");
- okButton->setCaption(
+ getWidget(mOkButton, "OKButton");
+ mOkButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {})));
- okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked);
+ mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mLStick = "#{sMouse}";
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ mControllerButtons.mY = "#{sSex}";
+ mControllerButtons.mL1 = "#{sHair}";
+ mControllerButtons.mR1 = "#{sFace}";
+ }
updateRaces();
updateSkills();
@@ -129,8 +127,17 @@ namespace MWGui
getWidget(okButton, "OKButton");
if (shown)
+ {
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {})));
+ mControllerButtons.mX = "#{sNext}";
+ }
+ else if (Settings::gui().mControllerMenus)
+ {
+ okButton->setCaption(
+ MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sDone", {})));
+ mControllerButtons.mX = "#{sDone}";
+ }
else
okButton->setCaption(
MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {})));
@@ -462,6 +469,55 @@ namespace MWGui
}
}
+ bool RaceDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onBackClicked(mBackButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkClicked(mOkButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y)
+ {
+ onSelectNextGender(nullptr);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ {
+ onSelectNextHair(nullptr);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ {
+ onSelectNextFace(nullptr);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+ winMgr->setKeyFocusWidget(mRaceList);
+ 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(mRaceList);
+ winMgr->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false);
+ }
+
+ return true;
+ }
+
+ bool RaceDialog::onControllerThumbstickEvent(const SDL_ControllerAxisEvent& arg)
+ {
+ if (arg.axis == SDL_CONTROLLER_AXIS_RIGHTX)
+ {
+ onPreviewScroll(nullptr, arg.value < 0 ? 1 : -1);
+ return true;
+ }
+
+ return false;
+ }
+
const ESM::NPC& RaceDialog::getResult() const
{
return mPreview->getPrototype();
diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp
index a6ac0e2813..0b00de40c0 100644
--- a/apps/openmw/mwgui/race.hpp
+++ b/apps/openmw/mwgui/race.hpp
@@ -101,6 +101,8 @@ namespace MWGui
MyGUI::ImageBox* mPreviewImage;
MyGUI::ListBox* mRaceList;
MyGUI::ScrollBar* mHeadRotate;
+ MyGUI::Button* mBackButton;
+ MyGUI::Button* mOkButton;
MyGUI::Widget* mSkillList;
std::vector mSkillItems;
@@ -118,6 +120,9 @@ namespace MWGui
std::unique_ptr mPreviewTexture;
bool mPreviewDirty;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ bool onControllerThumbstickEvent(const SDL_ControllerAxisEvent& arg) override;
};
}
#endif
diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp
index 7d57988d97..b6354c1d70 100644
--- a/apps/openmw/mwgui/recharge.cpp
+++ b/apps/openmw/mwgui/recharge.cpp
@@ -39,6 +39,10 @@ namespace MWGui
mBox->setDisplayMode(ItemChargeView::DisplayMode_EnchantmentCharge);
mGemIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onSelectItem);
+
+ mControllerButtons.mA = "#{OMWEngine:RechargeSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mY = "#{sSoulGem}";
}
void Recharge::onOpen()
@@ -136,4 +140,18 @@ namespace MWGui
updateView();
}
+ bool Recharge::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if ((arg.button == SDL_CONTROLLER_BUTTON_A && !mGemBox->getVisible()) || arg.button == SDL_CONTROLLER_BUTTON_Y)
+ {
+ onSelectItem(mGemIcon);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancel(mCancelButton);
+ else
+ mBox->onControllerButton(arg.button);
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp
index f8a037d2db..c10f96e71e 100644
--- a/apps/openmw/mwgui/recharge.hpp
+++ b/apps/openmw/mwgui/recharge.hpp
@@ -51,6 +51,8 @@ namespace MWGui
void onItemClicked(MyGUI::Widget* sender, const MWWorld::Ptr& item);
void onCancel(MyGUI::Widget* sender);
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp
index c1602b8407..cb3f6be8bb 100644
--- a/apps/openmw/mwgui/repair.cpp
+++ b/apps/openmw/mwgui/repair.cpp
@@ -39,6 +39,10 @@ namespace MWGui
mRepairBox->setDisplayMode(ItemChargeView::DisplayMode_Health);
mToolIcon->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onSelectItem);
+
+ mControllerButtons.mA = "#{sRepair}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mY = "#{OMWEngine:RepairTool}";
}
void Repair::onOpen()
@@ -150,4 +154,18 @@ namespace MWGui
updateRepairView();
}
+ bool Repair::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if ((arg.button == SDL_CONTROLLER_BUTTON_A && !mToolBox->getVisible()) || arg.button == SDL_CONTROLLER_BUTTON_Y)
+ {
+ onSelectItem(mToolIcon);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancel(mCancelButton);
+ else
+ mRepairBox->onControllerButton(arg.button);
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp
index 093a10e3fa..986b28b613 100644
--- a/apps/openmw/mwgui/repair.hpp
+++ b/apps/openmw/mwgui/repair.hpp
@@ -50,6 +50,8 @@ namespace MWGui
void onRepairItem(MyGUI::Widget* sender, const MWWorld::Ptr& ptr);
void onCancel(MyGUI::Widget* sender);
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
};
}
diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp
index ddce2c5f50..66b48aa2ee 100644
--- a/apps/openmw/mwgui/review.cpp
+++ b/apps/openmw/mwgui/review.cpp
@@ -36,6 +36,7 @@ namespace MWGui
ReviewDialog::ReviewDialog()
: WindowModal("openmw_chargen_review.layout")
, mUpdateSkillArea(false)
+ , mControllerFocus(5)
{
// Centre dialog
center();
@@ -46,21 +47,25 @@ namespace MWGui
getWidget(button, "NameButton");
adjustButtonSize(button);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked);
+ mButtons.push_back(button);
getWidget(mRaceWidget, "RaceText");
getWidget(button, "RaceButton");
adjustButtonSize(button);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked);
+ mButtons.push_back(button);
getWidget(mClassWidget, "ClassText");
getWidget(button, "ClassButton");
adjustButtonSize(button);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked);
+ mButtons.push_back(button);
getWidget(mBirthSignWidget, "SignText");
getWidget(button, "SignButton");
adjustButtonSize(button);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked);
+ mButtons.push_back(button);
// Setup dynamic stats
getWidget(mHealth, "Health");
@@ -108,10 +113,22 @@ namespace MWGui
MyGUI::Button* backButton;
getWidget(backButton, "BackButton");
backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked);
+ mButtons.push_back(backButton);
MyGUI::Button* okButton;
getWidget(okButton, "OKButton");
okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked);
+ mButtons.push_back(okButton);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ setControllerFocus(mButtons, mControllerFocus, true);
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ mControllerButtons.mX = "#{sDone}";
+ okButton->setCaption(
+ MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sDone", {})));
+ }
}
void ReviewDialog::onOpen()
@@ -522,4 +539,54 @@ 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)
+ {
+ 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_B)
+ {
+ onBackClicked(mButtons[4]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkClicked(mButtons[5]);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP || arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ {
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus - 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN || arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ setControllerFocus(mButtons, mControllerFocus, false);
+ mControllerFocus = wrap(mControllerFocus + 1, mButtons.size());
+ setControllerFocus(mButtons, mControllerFocus, true);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp
index 7226ad628d..cd3fc594a7 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;
};
}
#endif
diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp
index eec3b7bfe6..28c1b17cda 100644
--- a/apps/openmw/mwgui/savegamedialog.cpp
+++ b/apps/openmw/mwgui/savegamedialog.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -28,6 +29,7 @@
#include "../mwbase/world.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
+#include "../mwworld/timestamp.hpp"
#include "../mwstate/character.hpp"
@@ -64,6 +66,9 @@ namespace MWGui
// To avoid accidental deletions
mDeleteButton->setNeedKeyFocus(false);
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
}
void SaveGameDialog::onSlotActivated(MyGUI::ListBox* sender, size_t pos)
@@ -145,6 +150,28 @@ namespace MWGui
WindowModal::onOpen();
mSaveNameEdit->setCaption({});
+ if (Settings::gui().mControllerMenus && mSaving)
+ {
+ // For controller mode, set a default save file name. The format is
+ // "Day 24 - Last Steed 7 p.m."
+ const MWWorld::DateTimeManager& timeManager = *MWBase::Environment::get().getWorld()->getTimeManager();
+ std::string_view month = timeManager.getMonthName();
+ int hour = static_cast(timeManager.getTimeStamp().getHour());
+ bool pm = hour >= 12;
+ if (hour >= 13)
+ hour -= 12;
+ if (hour == 0)
+ hour = 12;
+
+ ESM::EpochTimeStamp currentDate = timeManager.getEpochTimeStamp();
+ std::string daysPassed
+ = Misc::StringUtils::format("#{Calendar:day} %i", timeManager.getTimeStamp().getDay());
+ std::string_view formattedHour(pm ? "#{Calendar:pm}" : "#{Calendar:am}");
+ std::string autoFilename = Misc::StringUtils::format(
+ "%s - %i %s %i %s", daysPassed, currentDate.mDay, month, hour, formattedHour);
+
+ mSaveNameEdit->setCaptionWithReplacing(autoFilename);
+ }
if (mSaving)
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit);
else
@@ -159,6 +186,13 @@ namespace MWGui
mSaveList->removeAllItems();
onSlotSelected(mSaveList, MyGUI::ITEM_NONE);
+ if (Settings::gui().mControllerMenus)
+ {
+ mOkButtonFocus = true;
+ mOkButton->setStateSelected(true);
+ mCancelButton->setStateSelected(false);
+ }
+
MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager();
if (mgr->characterBegin() == mgr->characterEnd())
return;
@@ -491,4 +525,55 @@ namespace MWGui
mScreenshotTexture = std::make_unique(texture);
mScreenshot->setRenderItemTexture(mScreenshotTexture.get());
}
+
+ ControllerButtons* SaveGameDialog::getControllerButtons()
+ {
+ mControllerButtons.mY = mSaving ? "" : "#{OMWEngine:LoadingSelectCharacter}";
+ return &mControllerButtons;
+ }
+
+ bool SaveGameDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mOkButtonFocus)
+ onOkButtonClicked(mOkButton);
+ else
+ onCancelButtonClicked(mCancelButton);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelButtonClicked(mCancelButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_Y)
+ {
+ size_t index = mCharacterSelection->getIndexSelected();
+ index = wrap(index + 1, mCharacterSelection->getItemCount());
+ mCharacterSelection->setIndexSelected(index);
+ onCharacterSelected(mCharacterSelection, index);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+ winMgr->setKeyFocusWidget(mSaveList);
+ 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(mSaveList);
+ winMgr->injectKeyPress(MyGUI::KeyCode::ArrowDown, 0, false);
+ }
+ else if ((arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && !mOkButtonFocus)
+ || (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && mOkButtonFocus))
+ {
+ mOkButtonFocus = !mOkButtonFocus;
+ mOkButton->setStateSelected(mOkButtonFocus);
+ mCancelButton->setStateSelected(!mOkButtonFocus);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp
index af831f066e..2b5b163044 100644
--- a/apps/openmw/mwgui/savegamedialog.hpp
+++ b/apps/openmw/mwgui/savegamedialog.hpp
@@ -24,6 +24,8 @@ namespace MWGui
void setLoadOrSave(bool load);
+ ControllerButtons* getControllerButtons() override;
+
private:
void confirmDeleteSave();
@@ -67,6 +69,9 @@ namespace MWGui
const MWState::Character* mCurrentCharacter;
const MWState::Slot* mCurrentSlot;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ bool mOkButtonFocus = true;
};
}
diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp
index 0b1658fd84..5b5f4a4ec4 100644
--- a/apps/openmw/mwgui/scrollwindow.cpp
+++ b/apps/openmw/mwgui/scrollwindow.cpp
@@ -7,6 +7,7 @@
#include
#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
@@ -38,6 +39,10 @@ namespace MWGui
mCloseButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ScrollWindow::onKeyButtonPressed);
mTakeButton->eventKeyButtonPressed += MyGUI::newDelegate(this, &ScrollWindow::onKeyButtonPressed);
+ mControllerScrollWidget = mTextView;
+ mControllerButtons.mB = "#{Interface:Close}";
+ mControllerButtons.mDpad = "#{sScrolldown}";
+
center();
}
@@ -115,4 +120,32 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll);
}
+
+ void ScrollWindow::onClose()
+ {
+ if (Settings::gui().mControllerMenus)
+ MWBase::Environment::get().getInputManager()->setGamepadGuiCursorEnabled(true);
+ BookWindowBase::onClose();
+ }
+
+ ControllerButtons* ScrollWindow::getControllerButtons()
+ {
+ mControllerButtons.mA = mTakeButton->getVisible() ? "#{sTake}" : "";
+ return &mControllerButtons;
+ }
+
+ bool ScrollWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (mTakeButton->getVisible())
+ onTakeButtonClicked(mTakeButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCloseButtonClicked(mCloseButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP || arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ return false; // Fall through to keyboard
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp
index 7daea98894..398843f824 100644
--- a/apps/openmw/mwgui/scrollwindow.hpp
+++ b/apps/openmw/mwgui/scrollwindow.hpp
@@ -20,15 +20,19 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& scroll) override;
void setInventoryAllowed(bool allowed);
+ void onClose() override;
void onResChange(int, int) override { center(); }
std::string_view getWindowIdForLua() const override { return "Scroll"; }
+ ControllerButtons* getControllerButtons() override;
+
protected:
void onCloseButtonClicked(MyGUI::Widget* _sender);
void onTakeButtonClicked(MyGUI::Widget* _sender);
void setTakeButtonShow(bool show);
void onKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char character);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
private:
Gui::ImageButton* mCloseButton;
diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp
index 77032623d2..15e083d94e 100644
--- a/apps/openmw/mwgui/settingswindow.cpp
+++ b/apps/openmw/mwgui/settingswindow.cpp
@@ -462,6 +462,10 @@ namespace MWGui
i++;
}
+
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:OK}";
+ mControllerButtons.mLStick = "#{sMouse}";
}
void SettingsWindow::onTabChanged(MyGUI::TabControl* /*_sender*/, size_t /*index*/)
@@ -471,7 +475,7 @@ namespace MWGui
void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender)
{
- setVisible(false);
+ MWBase::Environment::get().getWindowManager()->toggleSettingsWindow();
}
void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index)
@@ -1139,4 +1143,32 @@ namespace MWGui
mResolutionList->setScrollPosition(0);
mControlsBox->setViewOffset(MyGUI::IntPoint(0, 0));
}
+
+ bool SettingsWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onOkButtonClicked(mOkButton);
+ return true;
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ {
+ size_t index = mSettingsTab->getIndexSelected();
+ index = wrap(index - 1, mSettingsTab->getItemCount());
+ mSettingsTab->setIndexSelected(index);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ return true;
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ {
+ size_t index = mSettingsTab->getIndexSelected();
+ index = wrap(index + 1, mSettingsTab->getItemCount());
+ mSettingsTab->setIndexSelected(index);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ return true;
+ }
+
+ return false;
+ }
+
}
diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp
index 22a15eab97..0cc1c13fb4 100644
--- a/apps/openmw/mwgui/settingswindow.hpp
+++ b/apps/openmw/mwgui/settingswindow.hpp
@@ -29,6 +29,8 @@ namespace MWGui
void onResChange(int, int) override;
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+
protected:
MyGUI::TabControl* mSettingsTab;
MyGUI::Button* mOkButton;
diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp
index 2a67af5498..4a9f118d75 100644
--- a/apps/openmw/mwgui/spellbuyingwindow.cpp
+++ b/apps/openmw/mwgui/spellbuyingwindow.cpp
@@ -9,6 +9,7 @@
#include
#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -26,12 +27,21 @@ namespace MWGui
SpellBuyingWindow::SpellBuyingWindow()
: WindowBase("openmw_spell_buying_window.layout")
, mCurrentY(0)
+ , mControllerFocus(0)
{
getWidget(mCancelButton, "CancelButton");
getWidget(mPlayerGold, "PlayerGold");
getWidget(mSpellsView, "SpellsView");
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mDisableGamepadCursor = true;
+ mControllerButtons.mA = "#{sBuy}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mR3 = "#{sInfo}";
+ }
}
bool SpellBuyingWindow::sortSpells(const ESM::Spell* left, const ESM::Spell* right)
@@ -71,6 +81,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(std::make_pair(toAdd, mSpellsWidgetMap.size()));
}
void SpellBuyingWindow::clearSpells()
@@ -80,6 +92,7 @@ namespace MWGui
while (mSpellsView->getChildCount())
MyGUI::Gui::getInstance().destroyWidget(mSpellsView->getChildAt(0));
mSpellsWidgetMap.clear();
+ mSpellButtons.clear();
}
void SpellBuyingWindow::setPtr(const MWWorld::Ptr& actor)
@@ -130,6 +143,20 @@ namespace MWGui
updateLabels();
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = 0;
+ if (mSpellButtons.size() > 0)
+ {
+ mSpellButtons[0].first->setStateSelected(true);
+
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+ winMgr->setControllerTooltip(Settings::gui().mControllerTooltips);
+ if (winMgr->getControllerTooltip())
+ MWBase::Environment::get().getInputManager()->warpMouseToWidget(mSpellButtons[0].first);
+ }
+ }
+
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the
// scrollbar is hidden
mSpellsView->setVisibleVScroll(false);
@@ -200,4 +227,62 @@ 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)
+ {
+ if (mControllerFocus < mSpellButtons.size())
+ onSpellButtonClick(mSpellButtons[mControllerFocus].first);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelButtonClicked(mCancelButton);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK)
+ {
+ // Toggle info tooltip
+ MWBase::Environment::get().getWindowManager()->setControllerTooltip(
+ !MWBase::Environment::get().getWindowManager()->getControllerTooltip());
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ if (mSpellButtons.size() <= 1)
+ return true;
+
+ mSpellButtons[mControllerFocus].first->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus - 1, mSpellButtons.size());
+ mSpellButtons[mControllerFocus].first->setStateSelected(true);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (mSpellButtons.size() <= 1)
+ return true;
+
+ mSpellButtons[mControllerFocus].first->setStateSelected(false);
+ mControllerFocus = wrap(mControllerFocus + 1, mSpellButtons.size());
+ mSpellButtons[mControllerFocus].first->setStateSelected(true);
+ }
+ else
+ return true;
+
+ if (mControllerFocus < mSpellButtons.size())
+ {
+ // Scroll the list to keep the active item in view
+ size_t line = mSpellButtons[mControllerFocus].second;
+ if (line <= 5)
+ mSpellsView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ {
+ const int lineHeight = Settings::gui().mFontSize + 2;
+ mSpellsView->setViewOffset(MyGUI::IntPoint(0, -lineHeight * (line - 5)));
+ }
+
+ // Warp the mouse to the selected spell to show the tooltip
+ if (MWBase::Environment::get().getWindowManager()->getControllerTooltip())
+ MWBase::Environment::get().getInputManager()->warpMouseToWidget(mSpellButtons[mControllerFocus].first);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp
index 257b8a0df9..a53b57cc09 100644
--- a/apps/openmw/mwgui/spellbuyingwindow.hpp
+++ b/apps/openmw/mwgui/spellbuyingwindow.hpp
@@ -39,6 +39,8 @@ namespace MWGui
MyGUI::ScrollView* mSpellsView;
std::map mSpellsWidgetMap;
+ /// List of enabled/purchasable spells and their index in the full list.
+ std::vector> mSpellButtons;
void onCancelButtonClicked(MyGUI::Widget* _sender);
void onSpellButtonClick(MyGUI::Widget* _sender);
@@ -55,6 +57,8 @@ namespace MWGui
private:
static bool sortSpells(const ESM::Spell* left, const ESM::Spell* right);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ size_t mControllerFocus;
};
}
diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp
index 6bd9ef3ac8..5684e9ef2d 100644
--- a/apps/openmw/mwgui/spellcreationdialog.cpp
+++ b/apps/openmw/mwgui/spellcreationdialog.cpp
@@ -7,11 +7,13 @@
#include
#include
+#include
#include
#include
#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@@ -25,8 +27,8 @@
#include "../mwmechanics/spellutil.hpp"
#include "class.hpp"
+#include "textcolours.hpp"
#include "tooltips.hpp"
-#include "widgets.hpp"
namespace
{
@@ -95,6 +97,13 @@ namespace MWGui
+= MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged);
mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged);
mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mX = "#{Interface:OK}";
+ }
}
void EditEffectDialog::setConstantEffect(bool constant)
@@ -154,6 +163,15 @@ namespace MWGui
mMagnitudeMaxValue->setCaption(to + " 1");
mAreaValue->setCaption("0");
+ if (Settings::gui().mControllerMenus)
+ {
+ mRangeButton->setStateSelected(true);
+ mDeleteButton->setStateSelected(false);
+ mOkButton->setStateSelected(false);
+ mCancelButton->setStateSelected(false);
+ mControllerFocus = 0;
+ }
+
setVisible(true);
}
@@ -187,6 +205,15 @@ namespace MWGui
onDurationChanged(mDurationSlider, effect.mDuration - 1);
eventEffectModified(mEffect);
+ if (Settings::gui().mControllerMenus)
+ {
+ mRangeButton->setStateSelected(true);
+ mDeleteButton->setStateSelected(false);
+ mOkButton->setStateSelected(false);
+ mCancelButton->setStateSelected(false);
+ mControllerFocus = 0;
+ }
+
updateBoxes();
}
@@ -231,6 +258,25 @@ namespace MWGui
mAreaBox->setVisible(true);
// curY += mAreaBox->getSize().height;
}
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mButtons.clear();
+ mButtons.emplace_back(mRangeButton);
+ if (mMagnitudeBox->getVisible())
+ {
+ mButtons.emplace_back(mMagnitudeMinValue);
+ mButtons.emplace_back(mMagnitudeMaxValue);
+ }
+ if (mDurationBox->getVisible())
+ mButtons.emplace_back(mDurationValue);
+ if (mAreaBox->getVisible())
+ mButtons.emplace_back(mAreaValue);
+ if (mDeleteButton->getVisible())
+ mButtons.emplace_back(mDeleteButton);
+ mButtons.emplace_back(mOkButton);
+ mButtons.emplace_back(mCancelButton);
+ }
}
void EditEffectDialog::onRangeButtonClicked(MyGUI::Widget* sender)
@@ -340,6 +386,195 @@ namespace MWGui
eventEffectModified(mEffect);
}
+ bool EditEffectDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ int prevFocus = mControllerFocus;
+ mControllerFocus = std::clamp(mControllerFocus, 0, static_cast(mButtons.size()) - 1);
+ MyGUI::TextBox* button = mButtons[mControllerFocus];
+
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (button == mRangeButton)
+ onRangeButtonClicked(mRangeButton);
+ else if (button == mCancelButton)
+ onCancelButtonClicked(mCancelButton);
+ else if (button == mOkButton)
+ onOkButtonClicked(mOkButton);
+ else if (button == mDeleteButton)
+ onDeleteButtonClicked(mDeleteButton);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ onCancelButtonClicked(mCancelButton);
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onOkButtonClicked(mOkButton);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ if (mControllerFocus == 0)
+ mControllerFocus = static_cast(mButtons.size()) - 2;
+ else if (button == mCancelButton && mDeleteButton->getVisible())
+ mControllerFocus -= 3;
+ else if (button == mCancelButton || (button == mOkButton && mDeleteButton->getVisible()))
+ mControllerFocus -= 2;
+ else
+ mControllerFocus = std::max(mControllerFocus - 1, 0);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (button == mDeleteButton || button == mOkButton || button == mCancelButton)
+ mControllerFocus = 0;
+ else
+ mControllerFocus++;
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
+ {
+ if (button == mMagnitudeMinValue)
+ {
+ mMagnitudeMinSlider->setScrollPosition(0);
+ onMagnitudeMinChanged(nullptr, mMagnitudeMinSlider->getScrollPosition());
+ }
+ else if (button == mMagnitudeMaxValue)
+ {
+ mMagnitudeMaxSlider->setScrollPosition(mMagnitudeMinSlider->getScrollPosition());
+ onMagnitudeMaxChanged(nullptr, mMagnitudeMaxSlider->getScrollPosition());
+ }
+ else if (button == mDurationValue)
+ {
+ mDurationSlider->setScrollPosition(0);
+ onDurationChanged(nullptr, mDurationSlider->getScrollPosition());
+ }
+ else if (button == mAreaValue)
+ {
+ mAreaSlider->setScrollPosition(0);
+ onAreaChanged(nullptr, mAreaSlider->getScrollPosition());
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+ {
+ if (button == mMagnitudeMinValue)
+ {
+ mMagnitudeMinSlider->setScrollPosition(mMagnitudeMaxSlider->getScrollPosition());
+ onMagnitudeMinChanged(nullptr, mMagnitudeMinSlider->getScrollPosition());
+ }
+ else if (button == mMagnitudeMaxValue)
+ {
+ mMagnitudeMaxSlider->setScrollPosition(mMagnitudeMaxSlider->getScrollRange() - 1);
+ onMagnitudeMaxChanged(nullptr, mMagnitudeMaxSlider->getScrollPosition());
+ }
+ else if (button == mDurationValue)
+ {
+ mDurationSlider->setScrollPosition(mDurationSlider->getScrollRange() - 1);
+ onDurationChanged(nullptr, mDurationSlider->getScrollPosition());
+ }
+ else if (button == mAreaValue)
+ {
+ mAreaSlider->setScrollPosition(mAreaSlider->getScrollRange() - 1);
+ onAreaChanged(nullptr, mAreaSlider->getScrollPosition());
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+ {
+ if (button == mRangeButton)
+ onRangeButtonClicked(mRangeButton);
+ else if (button == mCancelButton)
+ mControllerFocus--;
+ else if (button == mOkButton && mDeleteButton->getVisible())
+ mControllerFocus--;
+ else if (button == mMagnitudeMinValue)
+ {
+ mMagnitudeMinSlider->setScrollPosition(mMagnitudeMinSlider->getScrollPosition() - 1);
+ onMagnitudeMinChanged(nullptr, mMagnitudeMinSlider->getScrollPosition());
+ }
+ else if (button == mMagnitudeMaxValue)
+ {
+ mMagnitudeMaxSlider->setScrollPosition(
+ std::max(mMagnitudeMaxSlider->getScrollPosition() - 1, mMagnitudeMinSlider->getScrollPosition()));
+ onMagnitudeMaxChanged(nullptr, mMagnitudeMaxSlider->getScrollPosition());
+ }
+ else if (button == mDurationValue)
+ {
+ mDurationSlider->setScrollPosition(mDurationSlider->getScrollPosition() - 1);
+ onDurationChanged(nullptr, mDurationSlider->getScrollPosition());
+ }
+ else if (button == mAreaValue)
+ {
+ mAreaSlider->setScrollPosition(mAreaSlider->getScrollPosition() - 1);
+ onAreaChanged(nullptr, mAreaSlider->getScrollPosition());
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+ {
+ if (button == mRangeButton)
+ onRangeButtonClicked(mRangeButton);
+ else if (button == mDeleteButton)
+ mControllerFocus++;
+ else if (button == mOkButton)
+ mControllerFocus++;
+ else if (button == mMagnitudeMinValue)
+ {
+ mMagnitudeMinSlider->setScrollPosition(
+ std::min(mMagnitudeMinSlider->getScrollPosition() + 1, mMagnitudeMaxSlider->getScrollPosition()));
+ onMagnitudeMinChanged(nullptr, mMagnitudeMinSlider->getScrollPosition());
+ }
+ else if (button == mMagnitudeMaxValue)
+ {
+ mMagnitudeMaxSlider->setScrollPosition(mMagnitudeMaxSlider->getScrollPosition() + 1);
+ onMagnitudeMaxChanged(nullptr, mMagnitudeMaxSlider->getScrollPosition());
+ }
+ else if (button == mDurationValue)
+ {
+ mDurationSlider->setScrollPosition(mDurationSlider->getScrollPosition() + 1);
+ onDurationChanged(nullptr, mDurationSlider->getScrollPosition());
+ }
+ else if (button == mAreaValue)
+ {
+ mAreaSlider->setScrollPosition(mAreaSlider->getScrollPosition() + 1);
+ onAreaChanged(nullptr, mAreaSlider->getScrollPosition());
+ }
+ }
+
+ if (prevFocus != mControllerFocus)
+ updateControllerFocus(prevFocus, mControllerFocus);
+
+ return true;
+ }
+
+ void EditEffectDialog::updateControllerFocus(int prevFocus, int newFocus)
+ {
+ const TextColours& textColours{ MWBase::Environment::get().getWindowManager()->getTextColours() };
+
+ if (prevFocus >= 0 && prevFocus < static_cast(mButtons.size()))
+ {
+ MyGUI::TextBox* button = mButtons[prevFocus];
+ if (button == mMagnitudeMinValue || button == mMagnitudeMaxValue || button == mDurationValue
+ || button == mAreaValue)
+ {
+ button->setTextColour(textColours.normal);
+ }
+ else
+ {
+ static_cast(button)->setStateSelected(false);
+ }
+ }
+
+ if (newFocus >= 0 && newFocus < static_cast(mButtons.size()))
+ {
+ MyGUI::TextBox* button = mButtons[newFocus];
+ if (button == mMagnitudeMinValue || button == mMagnitudeMaxValue || button == mDurationValue
+ || button == mAreaValue)
+ {
+ button->setTextColour(textColours.link);
+ }
+ else
+ {
+ static_cast(button)->setStateSelected(true);
+ }
+ }
+ }
+
// ------------------------------------------------------------------------------------------------
SpellCreationDialog::SpellCreationDialog()
@@ -361,6 +596,14 @@ namespace MWGui
mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SpellCreationDialog::onAccept);
setWidgets(mAvailableEffectsList, mUsedEffectsView);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{Interface:Cancel}";
+ mControllerButtons.mX = "#{sBuy}";
+ mControllerButtons.mR3 = "#{sInfo}";
+ }
}
void SpellCreationDialog::setPtr(const MWWorld::Ptr& actor)
@@ -495,6 +738,22 @@ namespace MWGui
mSuccessChance->setCaption(MyGUI::utility::toString(intChance));
}
+ bool SpellCreationDialog::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ {
+ onCancelButtonClicked(mCancelButton);
+ return true;
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_X)
+ {
+ onBuyButtonClicked(mBuyButton);
+ return true;
+ }
+ else
+ return EffectEditorBase::onControllerButtonEvent(arg);
+ }
+
// ------------------------------------------------------------------------------------------------
EffectEditorBase::EffectEditorBase(Type type)
@@ -566,6 +825,7 @@ namespace MWGui
mAvailableEffectsList->adjustSize();
mAvailableEffectsList->scrollToTop();
+ mAvailableButtons.clear();
for (const short effectId : knownEffects)
{
const std::string& name = MWBase::Environment::get()
@@ -573,13 +833,27 @@ namespace MWGui
->get()
.find(ESM::MagicEffect::indexToGmstString(effectId))
->mValue.getString();
- MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name);
+ MyGUI::Button* w = mAvailableEffectsList->getItemWidget(name);
+ mAvailableButtons.emplace_back(w);
ToolTips::createMagicEffectToolTip(w, effectId);
}
mEffects.clear();
updateEffectsView();
+
+ if (Settings::gui().mControllerMenus)
+ {
+ mAvailableFocus = 0;
+ mEffectFocus = 0;
+ mRightColumn = false;
+ if (mAvailableButtons.size() > 0)
+ {
+ mAvailableButtons[0]->setStateSelected(true);
+ if (MWBase::Environment::get().getWindowManager()->getControllerTooltip())
+ MWBase::Environment::get().getInputManager()->warpMouseToWidget(mAvailableButtons[0]);
+ }
+ }
}
void EffectEditorBase::setWidgets(Gui::MWList* availableEffectsList, MyGUI::ScrollView* usedEffectsView)
@@ -691,6 +965,7 @@ namespace MWGui
MyGUI::IntSize size(0, 0);
+ mEffectButtons.clear();
int i = 0;
for (const ESM::ENAMstruct& effectInfo : mEffects)
{
@@ -723,6 +998,8 @@ namespace MWGui
size.width = std::max(size.width, effect->getRequestedWidth());
size.height += 24;
++i;
+
+ mEffectButtons.emplace_back(std::pair(effect, button));
}
// Canvas size must be expressed with HScroll disabled, otherwise MyGUI would expand the scroll area when the
@@ -760,4 +1037,102 @@ namespace MWGui
effect.mRange = ESM::RT_Self;
mConstantEffect = constant;
}
+
+ bool EffectEditorBase::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+
+ if (arg.button == SDL_CONTROLLER_BUTTON_A)
+ {
+ if (!mRightColumn && mAvailableFocus >= 0 && mAvailableFocus < static_cast(mAvailableButtons.size()))
+ {
+ onAvailableEffectClicked(mAvailableButtons[mAvailableFocus]);
+ winMgr->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ else if (mRightColumn && mEffectFocus >= 0 && mEffectFocus < static_cast(mEffectButtons.size()))
+ {
+ onEditEffect(mEffectButtons[mEffectFocus].second);
+ winMgr->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK)
+ {
+ // Toggle info tooltip
+ winMgr->setControllerTooltip(!mRightColumn && !winMgr->getControllerTooltip());
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
+ {
+ if (mRightColumn && mEffectButtons.size() > 0)
+ {
+ if (mEffectFocus >= 0 && mEffectFocus < static_cast(mEffectButtons.size()))
+ mEffectButtons[mEffectFocus].first->setStateSelected(false);
+ mEffectFocus = wrap(mEffectFocus - 1, mEffectButtons.size());
+ mEffectButtons[mEffectFocus].first->setStateSelected(true);
+ }
+ else if (!mRightColumn && mAvailableButtons.size() > 0)
+ {
+ if (mAvailableFocus >= 0 && mAvailableFocus < static_cast(mAvailableButtons.size()))
+ mAvailableButtons[mAvailableFocus]->setStateSelected(false);
+ mAvailableFocus = wrap(mAvailableFocus - 1, mAvailableButtons.size());
+ mAvailableButtons[mAvailableFocus]->setStateSelected(true);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+ {
+ if (mRightColumn && mEffectButtons.size() > 0)
+ {
+ if (mEffectFocus >= 0 && mEffectFocus < static_cast(mEffectButtons.size()))
+ mEffectButtons[mEffectFocus].first->setStateSelected(false);
+ mEffectFocus = wrap(mEffectFocus + 1, mEffectButtons.size());
+ mEffectButtons[mEffectFocus].first->setStateSelected(true);
+ }
+ else if (!mRightColumn && mAvailableButtons.size() > 0)
+ {
+ if (mAvailableFocus >= 0 && mAvailableFocus < static_cast(mAvailableButtons.size()))
+ mAvailableButtons[mAvailableFocus]->setStateSelected(false);
+ mAvailableFocus = wrap(mAvailableFocus + 1, mAvailableButtons.size());
+ mAvailableButtons[mAvailableFocus]->setStateSelected(true);
+ }
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && mRightColumn)
+ {
+ mRightColumn = false;
+ if (mEffectFocus >= 0 && mEffectFocus < static_cast(mEffectButtons.size()))
+ mEffectButtons[mEffectFocus].first->setStateSelected(false);
+ if (mAvailableFocus >= 0 && mAvailableFocus < static_cast(mAvailableButtons.size()))
+ mAvailableButtons[mAvailableFocus]->setStateSelected(true);
+
+ winMgr->setControllerTooltip(Settings::gui().mControllerTooltips);
+ }
+ else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && !mRightColumn && mEffectButtons.size() > 0)
+ {
+ mRightColumn = true;
+ if (mAvailableFocus >= 0 && mAvailableFocus < static_cast(mAvailableButtons.size()))
+ mAvailableButtons[mAvailableFocus]->setStateSelected(false);
+ if (mEffectFocus >= 0 && mEffectFocus < static_cast(mEffectButtons.size()))
+ mEffectButtons[mEffectFocus].first->setStateSelected(true);
+
+ winMgr->setControllerTooltip(false);
+ }
+ else
+ return true;
+
+ // Scroll the list to keep the active item in view
+ if (mAvailableFocus <= 5)
+ mAvailableEffectsList->setViewOffset(0);
+ else
+ {
+ const int lineHeight = Settings::gui().mFontSize + 3;
+ mAvailableEffectsList->setViewOffset(-lineHeight * (mAvailableFocus - 5));
+ }
+
+ if (!mRightColumn && mAvailableFocus >= 0 && mAvailableFocus < static_cast(mAvailableButtons.size()))
+ {
+ // Warp the mouse to the selected spell to show the tooltip
+ if (winMgr->getControllerTooltip())
+ MWBase::Environment::get().getInputManager()->warpMouseToWidget(mAvailableButtons[mAvailableFocus]);
+ }
+
+ return true;
+ }
}
diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp
index 0887dd8c94..f2c440d305 100644
--- a/apps/openmw/mwgui/spellcreationdialog.hpp
+++ b/apps/openmw/mwgui/spellcreationdialog.hpp
@@ -7,6 +7,7 @@
#include
#include "referenceinterface.hpp"
+#include "widgets.hpp"
#include "windowbase.hpp"
namespace Gui
@@ -83,13 +84,18 @@ namespace MWGui
void updateBoxes();
- protected:
+ private:
ESM::ENAMstruct mEffect;
ESM::ENAMstruct mOldEffect;
const ESM::MagicEffect* mMagicEffect;
bool mConstantEffect;
+
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void updateControllerFocus(int prevFocus, int newFocus);
+ int mControllerFocus;
+ std::vector mButtons;
};
class EffectEditorBase
@@ -142,8 +148,16 @@ namespace MWGui
virtual void notifyEffectsChanged() {}
+ virtual bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg);
+
private:
Type mType;
+
+ int mAvailableFocus;
+ int mEffectFocus;
+ bool mRightColumn;
+ std::vector mAvailableButtons;
+ std::vector> mEffectButtons;
};
class SpellCreationDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase
@@ -166,6 +180,7 @@ namespace MWGui
void onCancelButtonClicked(MyGUI::Widget* sender);
void onBuyButtonClicked(MyGUI::Widget* sender);
void onAccept(MyGUI::EditBox* sender);
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
void notifyEffectsChanged() override;
diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp
index 678f6ffe1f..ff77284bdf 100644
--- a/apps/openmw/mwgui/spellview.cpp
+++ b/apps/openmw/mwgui/spellview.cpp
@@ -9,7 +9,12 @@
#include
#include
+#include "../mwbase/environment.hpp"
+#include "../mwbase/inputmanager.hpp"
+#include "../mwbase/windowmanager.hpp"
+
#include "tooltips.hpp"
+#include "windowbase.hpp"
namespace MWGui
{
@@ -28,6 +33,8 @@ namespace MWGui
: mScrollView(nullptr)
, mShowCostColumn(true)
, mHighlightSelected(true)
+ , mControllerActiveWindow(false)
+ , mControllerFocus(0)
{
}
@@ -88,6 +95,8 @@ namespace MWGui
const int spellHeight = Settings::gui().mFontSize + 2;
mLines.clear();
+ mButtons.clear();
+ mGroupIndices.clear();
while (mScrollView->getChildCount())
MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0));
@@ -106,7 +115,9 @@ namespace MWGui
curType = spell.mType;
}
- const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped";
+ std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped";
+ if (Settings::gui().mControllerMenus)
+ skin = spell.mActive ? "SpellTextEquippedController" : "SpellTextUnequippedController";
const std::string captionSuffix = MWGui::ToolTips::getCountString(spell.mCount);
Gui::SharedStateButton* t = mScrollView->createWidget(
@@ -115,6 +126,7 @@ namespace MWGui
t->setCaption(spell.mName + captionSuffix);
t->setTextAlign(MyGUI::Align::Left);
adjustSpellWidget(spell, i, t);
+ mButtons.emplace_back(std::make_pair(t, i));
if (!spell.mCostColumn.empty() && mShowCostColumn)
{
@@ -132,7 +144,7 @@ namespace MWGui
mLines.emplace_back(t, costChance, i);
}
else
- mLines.emplace_back(t, (MyGUI::Widget*)nullptr, i);
+ mLines.emplace_back(t, static_cast(nullptr), i);
t->setStateSelected(spell.mSelected);
}
@@ -221,6 +233,12 @@ namespace MWGui
height += lineHeight;
}
+ if (Settings::gui().mControllerMenus)
+ {
+ mControllerFocus = wrap(mControllerFocus, mButtons.size());
+ updateControllerFocus(-1, mControllerFocus);
+ }
+
// Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the
// scrollbar is hidden
mScrollView->setVisibleVScroll(false);
@@ -235,7 +253,7 @@ namespace MWGui
MyGUI::ImageBox* separator = mScrollView->createWidget(
"MW_HLine", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), MyGUI::Align::Left | MyGUI::Align::Top);
separator->setNeedMouseFocus(false);
- mLines.emplace_back(separator, (MyGUI::Widget*)nullptr, NoSpellIndex);
+ mLines.emplace_back(separator, static_cast(nullptr), NoSpellIndex);
}
MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText",
@@ -255,7 +273,9 @@ namespace MWGui
mLines.emplace_back(groupWidget, groupWidget2, NoSpellIndex);
}
else
- mLines.emplace_back(groupWidget, (MyGUI::Widget*)nullptr, NoSpellIndex);
+ mLines.emplace_back(groupWidget, static_cast(nullptr), NoSpellIndex);
+
+ mGroupIndices.push_back(mButtons.size());
}
void SpellView::setSize(const MyGUI::IntSize& _value)
@@ -316,4 +336,124 @@ namespace MWGui
{
mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
}
+
+ void SpellView::setActiveControllerWindow(bool active)
+ {
+ mControllerActiveWindow = active;
+ if (active)
+ update();
+ }
+
+ void SpellView::onControllerButton(const unsigned char button)
+ {
+ if (mButtons.empty())
+ return;
+
+ int prevFocus = mControllerFocus;
+
+ switch (button)
+ {
+ case SDL_CONTROLLER_BUTTON_A:
+ // Select the focused item, if any.
+ if (mControllerFocus >= 0 && mControllerFocus < static_cast(mButtons.size()))
+ {
+ onSpellSelected(mButtons[mControllerFocus].first);
+ MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Menu Click"));
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
+ // Toggle info tooltip
+ MWBase::Environment::get().getWindowManager()->setControllerTooltip(
+ !MWBase::Environment::get().getWindowManager()->getControllerTooltip());
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_UP:
+ mControllerFocus--;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
+ mControllerFocus++;
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
+ mControllerFocus = std::max(0, mControllerFocus - 10);
+ break;
+ case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
+ mControllerFocus = std::min(mControllerFocus + 10, static_cast(mButtons.size()) - 1);
+ break;
+ case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
+ {
+ // Jump to first item in previous group
+ int prevGroupIndex = 0;
+ for (int groupIndex : mGroupIndices)
+ {
+ if (groupIndex >= mControllerFocus)
+ break;
+ else
+ prevGroupIndex = groupIndex;
+ }
+ mControllerFocus = prevGroupIndex;
+ }
+ break;
+ case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
+ {
+ // Jump to first item in next group
+ int newFocus = mControllerFocus;
+ for (int groupIndex : mGroupIndices)
+ {
+ if (groupIndex > mControllerFocus)
+ {
+ newFocus = groupIndex;
+ break;
+ }
+ }
+ // If on last group, jump to bottom of whole list
+ if (newFocus == mControllerFocus)
+ newFocus = mButtons.size() - 1;
+ mControllerFocus = newFocus;
+ }
+ break;
+ default:
+ return;
+ }
+
+ mControllerFocus = wrap(mControllerFocus, mButtons.size());
+
+ if (prevFocus != mControllerFocus)
+ updateControllerFocus(prevFocus, mControllerFocus);
+ else
+ updateControllerFocus(-1, mControllerFocus);
+ }
+
+ void SpellView::updateControllerFocus(int prevFocus, int newFocus)
+ {
+ if (mButtons.empty())
+ return;
+
+ if (prevFocus >= 0 && prevFocus < static_cast(mButtons.size()))
+ {
+ Gui::SharedStateButton* prev = mButtons[prevFocus].first;
+ if (prev)
+ prev->onMouseLostFocus(nullptr);
+ }
+
+ if (mControllerActiveWindow && newFocus >= 0 && newFocus < static_cast(mButtons.size()))
+ {
+ Gui::SharedStateButton* focused = mButtons[newFocus].first;
+ if (focused)
+ {
+ focused->onMouseSetFocus(nullptr);
+
+ // Scroll the list to keep the active item in view
+ int line = mButtons[newFocus].second;
+ if (line <= 5)
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, 0));
+ else
+ {
+ const int lineHeight = focused->getHeight();
+ mScrollView->setViewOffset(MyGUI::IntPoint(0, -lineHeight * (line - 5)));
+ }
+
+ if (MWBase::Environment::get().getWindowManager()->getControllerTooltip())
+ MWBase::Environment::get().getInputManager()->warpMouseToWidget(focused);
+ }
+ }
+ }
}
diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp
index caff43a33e..7895b86ec8 100644
--- a/apps/openmw/mwgui/spellview.hpp
+++ b/apps/openmw/mwgui/spellview.hpp
@@ -13,6 +13,11 @@ namespace MyGUI
class ScrollView;
}
+namespace Gui
+{
+ class SharedStateButton;
+}
+
namespace MWGui
{
@@ -54,6 +59,9 @@ namespace MWGui
void resetScrollbars();
+ void setActiveControllerWindow(bool active);
+ void onControllerButton(const unsigned char button);
+
private:
MyGUI::ScrollView* mScrollView;
@@ -89,6 +97,15 @@ namespace MWGui
void addGroup(const std::string& label1, const std::string& label2);
void adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget);
+ /// Keep a list of buttons for controller navigation and their index in the full list.
+ std::vector> mButtons;
+ /// Keep a list of group offsets for controller navigation
+ std::vector mGroupIndices;
+
+ bool mControllerActiveWindow;
+ int mControllerFocus;
+ void updateControllerFocus(int prevFocus, int newFocus);
+
void onSpellSelected(MyGUI::Widget* _sender);
void onMouseWheelMoved(MyGUI::Widget* _sender, int _rel);
diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp
index 566b7f4ccd..78c7fabbff 100644
--- a/apps/openmw/mwgui/spellwindow.cpp
+++ b/apps/openmw/mwgui/spellwindow.cpp
@@ -2,6 +2,8 @@
#include
#include
+#include
+#include
#include
#include
@@ -27,6 +29,7 @@
#include "confirmationdialog.hpp"
#include "spellicons.hpp"
#include "spellview.hpp"
+#include "statswindow.hpp"
namespace MWGui
{
@@ -55,6 +58,14 @@ namespace MWGui
// Adjust the spell filtering widget size because of MyGUI limitations.
int filterWidth = mSpellView->getSize().width - deleteButton->getSize().width - 3;
mFilterEdit->setSize(filterWidth, mFilterEdit->getSize().height);
+
+ if (Settings::gui().mControllerMenus)
+ {
+ setPinButtonVisible(false);
+ mControllerButtons.mA = "#{sSelect}";
+ mControllerButtons.mB = "#{sBack}";
+ mControllerButtons.mR3 = "#{sInfo}";
+ }
}
void SpellWindow::onPinToggled()
@@ -66,7 +77,9 @@ namespace MWGui
void SpellWindow::onTitleDoubleClicked()
{
- if (MyGUI::InputManager::getInstance().isShiftPressed())
+ if (Settings::gui().mControllerMenus)
+ return;
+ else if (MyGUI::InputManager::getInstance().isShiftPressed())
MWBase::Environment::get().getWindowManager()->toggleMaximized(this);
else if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic);
@@ -288,4 +301,39 @@ namespace MWGui
onSpellSelected(selectedSpell.mId);
}
}
+
+ bool SpellWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg)
+ {
+ if (arg.button == SDL_CONTROLLER_BUTTON_B)
+ MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
+ else
+ mSpellView->onControllerButton(arg.button);
+
+ return true;
+ }
+
+ void SpellWindow::setActiveControllerWindow(bool active)
+ {
+ MWBase::WindowManager* winMgr = MWBase::Environment::get().getWindowManager();
+ if (winMgr->getMode() == MWGui::GM_Inventory)
+ {
+ // Fill the screen, or limit to a certain size on large screens. Size chosen to
+ // match the size of the stats window.
+ MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
+ int width = std::min(viewSize.width, StatsWindow::getIdealWidth());
+ int height = std::min(winMgr->getControllerMenuHeight(), StatsWindow::getIdealHeight());
+ int x = (viewSize.width - width) / 2;
+ int y = (viewSize.height - height) / 2;
+
+ MyGUI::Window* window = mMainWidget->castType();
+ window->setCoord(x, active ? y : viewSize.height + 1, width, height);
+
+ MWBase::Environment::get().getWindowManager()->setControllerTooltip(
+ active && Settings::gui().mControllerTooltips);
+ }
+
+ mSpellView->setActiveControllerWindow(active);
+
+ WindowBase::setActiveControllerWindow(active);
+ }
}
diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp
index e35c5cdc4c..c27ec276a3 100644
--- a/apps/openmw/mwgui/spellwindow.hpp
+++ b/apps/openmw/mwgui/spellwindow.hpp
@@ -41,6 +41,8 @@ namespace MWGui
void onPinToggled() override;
void onTitleDoubleClicked() override;
void onOpen() override;
+ bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override;
+ void setActiveControllerWindow(bool active) override;
SpellView* mSpellView;
std::unique_ptr mSpellIcons;
diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp
index 69f0b4b449..9ae598052a 100644
--- a/apps/openmw/mwgui/statswindow.cpp
+++ b/apps/openmw/mwgui/statswindow.cpp
@@ -6,6 +6,7 @@
#include
#include