diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 5ee20476b3..f5adc42340 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -61,6 +62,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; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 5302270ee0..27331699cf 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -11,6 +11,7 @@ #include +#include "../mwgui/hud.hpp" #include "../mwgui/mode.hpp" #include "../mwgui/windowbase.hpp" @@ -158,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; diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 97f3a0072e..21869c58f5 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWGUI_COMPANIONWINDOW_H #define OPENMW_MWGUI_COMPANIONWINDOW_H +#include "companionitemmodel.hpp" +#include "itemmodel.hpp" #include "referenceinterface.hpp" #include "windowbase.hpp" @@ -32,6 +34,9 @@ namespace MWGui std::string_view getWindowIdForLua() const override { return "Companion"; } + MWGui::ItemView* getItemView() { return mItemView; } + ItemModel* getModel() { return mModel; } + private: ItemView* mItemView; SortFilterItemModel* mSortModel; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 54eb6122c5..c0896d5b5e 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -58,9 +58,9 @@ namespace MWGui setCoord(200, 0, 600, 300); mControllerButtons.a = "#{sTake}"; - mControllerButtons.b = "#{sClose}"; + mControllerButtons.b = "#{sBack}"; mControllerButtons.x = "#{sTakeAll}"; - mControllerButtons.y = "#{sInfo}"; + mControllerButtons.r3 = "#{sInfo}"; mControllerButtons.l2 = "#{sInventory}"; } @@ -385,12 +385,13 @@ namespace MWGui if (mDisposeCorpseButton->getVisible()) onDisposeCorpseButtonClicked(mDisposeCorpseButton); } - else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP || + 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->onControllerButtonEvent(arg); + mItemView->onControllerButtonEvent(arg.button); } return true; diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index 08497e0369..96ee6a6380 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -41,8 +41,12 @@ namespace MWGui std::string_view getWindowIdForLua() const override { return "Container"; } ControllerButtonStr* getControllerButtons() override; + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; void setActiveControllerWindow(bool active) override; + MWGui::ItemView* getItemView() { return mItemView; } + ItemModel* getModel() { return mModel; } + private: DragAndDrop* mDragAndDrop; @@ -68,8 +72,6 @@ namespace MWGui bool onTakeItem(const ItemStack& item, int count); void onReferenceUnavailable() override; - - bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; }; } #endif // CONTAINER_H diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 0a37c93b4f..a62120fc10 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -243,6 +243,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()) @@ -252,15 +263,12 @@ namespace MWGui if (mDragAndDrop->mIsOnDragAndDrop) { // drop item into the gameworld - MWBase::Environment::get().getWorld()->breakInvisibility(MWMechanics::getPlayer()); - MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); - WorldItemModel drop(mouseX, mouseY); - mDragAndDrop->drop(&drop, nullptr); + dropDraggedItem(mouseX, mouseY); winMgr->changePointer("arrow"); } 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/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index a773b4635b..27c4bbf57d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -31,8 +31,11 @@ #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 "itemview.hpp" #include "settings.hpp" @@ -127,6 +130,13 @@ namespace MWGui setGuiMode(mGuiMode); + if (Settings::gui().mControllerMenus) + { + mControllerButtons.b = "#{sBack}"; + mControllerButtons.r1 = "Filter"; + mControllerButtons.r3 = "#{sInfo}"; + } + adjustPanes(); } @@ -302,7 +312,9 @@ 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 && !(Settings::gui().mControllerMenus && mGuiMode == MWGui::GM_Inventory)) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}"; @@ -866,4 +878,147 @@ namespace MWGui const MyGUI::IntSize viewport = getPreviewViewportSize(); return osg::Vec2f(normalisedX * float(viewport.width - 1), (1.0 - normalisedY) * float(viewport.height - 1)); } + + ControllerButtonStr* InventoryWindow::getControllerButtons() + { + switch (mGuiMode) + { + case MWGui::GM_Companion: + case MWGui::GM_Container: + mControllerButtons.a = "Put"; + mControllerButtons.x = "#{sTakeAll}"; + mControllerButtons.y = ""; + mControllerButtons.r2 = "#{sContainer}"; + break; + case MWGui::GM_Barter: + mControllerButtons.a = "#{sSell}"; + mControllerButtons.x = ""; + mControllerButtons.y = ""; + mControllerButtons.r2 = "#{sBarter}"; + break; + case MWGui::GM_Inventory: + default: + mControllerButtons.a = "#{sEquip}"; + mControllerButtons.x = "#{sDrop}"; + mControllerButtons.y = "#{sUnequip}"; + mControllerButtons.r2 = ""; + break; + } + return &mControllerButtons; + } + + bool InventoryWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_B) + { + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + mItemView->onControllerButtonEvent(SDL_CONTROLLER_BUTTON_A); + // The following actions are done here, not in onItemSelectedFromSourceModel, because we + // want the mouse to work even in controller mode. + if (mGuiMode == MWGui::GM_Inventory && mDragAndDrop->mIsOnDragAndDrop) + { + // Drag and drop the item on the avatar to activate it. + onAvatarClicked(nullptr); // Equip or use + // 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 (mGuiMode == MWGui::GM_Companion && mDragAndDrop->mIsOnDragAndDrop) + { + // Drag and drop the item on the companion's window. + MWGui::CompanionWindow* companionWindow = + (MWGui::CompanionWindow *)MWBase::Environment::get().getWindowManager()->getGuiModeWindows(mGuiMode).at(0); + mDragAndDrop->drop(companionWindow->getModel(), companionWindow->getItemView()); + } + else if (mGuiMode == MWGui::GM_Container && mDragAndDrop->mIsOnDragAndDrop) + { + // Drag and drop the item on the container window. + MWGui::ContainerWindow* containerWindow = + (MWGui::ContainerWindow *)MWBase::Environment::get().getWindowManager()->getGuiModeWindows(mGuiMode).at(0); + mDragAndDrop->drop(containerWindow->getModel(), containerWindow->getItemView()); + } + // GM_Barter is handled by onControllerButtonEvent. No other steps are necessary. + } + else if (arg.button == SDL_CONTROLLER_BUTTON_X) + { + if (mGuiMode == MWGui::GM_Inventory) + { + // Drop the item into the gameworld + mItemView->onControllerButtonEvent(SDL_CONTROLLER_BUTTON_A); + if (mDragAndDrop->mIsOnDragAndDrop) + MWBase::Environment::get().getWindowManager()->getHud()->dropDraggedItem(0.5f, 0.5f); + } + 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 = + (MWGui::ContainerWindow *)MWBase::Environment::get().getWindowManager()->getGuiModeWindows(mGuiMode).at(0); + containerWindow->onControllerButtonEvent(arg); + } + } + else if (arg.button == SDL_CONTROLLER_BUTTON_Y) + { + if (mGuiMode == MWGui::GM_Inventory) + { + // Unequip an item. + mItemView->onControllerButtonEvent(SDL_CONTROLLER_BUTTON_A); + onBackgroundSelected(); // Drop on inventory background to unequip + } + } + 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); + } + 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); + } + else + { + mItemView->onControllerButtonEvent(arg.button); + } + + return true; + } + + void InventoryWindow::setActiveControllerWindow(bool active) + { + if (!Settings::gui().mControllerMenus) + return; + + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Inventory) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::Window* window = mMainWidget->castType(); + window->setCoord(0, active ? 0 : viewSize.height + 1, viewSize.width, viewSize.height - 48); + + adjustPanes(); + updatePreviewSize(); + } + + mItemView->setActiveControllerWindow(active); + WindowBase::setActiveControllerWindow(active); + } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 9fc77ceec5..5b4d61f272 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -67,8 +67,12 @@ namespace MWGui std::string_view getWindowIdForLua() const override { return "Inventory"; } + ControllerButtonStr* getControllerButtons() override; + protected: void onTitleDoubleClicked() override; + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; + void setActiveControllerWindow(bool active) override; private: DragAndDrop* mDragAndDrop; diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 526f231956..a223bde5e0 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -9,6 +9,10 @@ #include +#include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" +#include "../mwbase/windowmanager.hpp" + #include "itemmodel.hpp" #include "itemwidget.hpp" @@ -75,8 +79,8 @@ namespace MWGui if (Settings::gui().mControllerMenus) { - if (mControllerFocus >= mItemCount) - mControllerFocus = mItemCount - 1; + mControllerTooltip = false; + mControllerFocus = std::clamp(mControllerFocus, 0, mItemCount - 1); updateControllerFocus(-1, mControllerFocus); } @@ -182,26 +186,49 @@ namespace MWGui void ItemView::setActiveControllerWindow(bool active) { + mControllerActiveWindow = active; + + if (mControllerTooltip) + { + MWBase::Environment::get().getWindowManager()->setCursorActive(false); + mControllerTooltip = false; + } + if (active) updateControllerFocus(-1, mControllerFocus); else updateControllerFocus(mControllerFocus, -1); } - void ItemView::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + void ItemView::onControllerButtonEvent(const unsigned char button) { if (!mItemCount) return; int prevFocus = mControllerFocus; - if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP && mControllerFocus % mRows != 0) + if (button == 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)); + } + } + else if (button == SDL_CONTROLLER_BUTTON_RIGHTSTICK) + { + // Toggle info tooltip + mControllerTooltip = !mControllerTooltip; + updateControllerFocus(-1, mControllerFocus); + } + else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP && mControllerFocus % mRows != 0) mControllerFocus--; - else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN && mControllerFocus % mRows != mRows - 1) + else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN && mControllerFocus % mRows != mRows - 1) mControllerFocus++; - else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && mControllerFocus >= mRows) + else if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT && mControllerFocus >= mRows) mControllerFocus -= mRows; - else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && mControllerFocus + mRows < mItemCount) + else if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && mControllerFocus + mRows < mItemCount) mControllerFocus += mRows; if (mControllerFocus < 0) @@ -213,25 +240,29 @@ namespace MWGui updateControllerFocus(prevFocus, mControllerFocus); } - void ItemView::updateControllerFocus(int _prevFocus, int _newFocus) + void ItemView::updateControllerFocus(int prevFocus, int newFocus) { if (!mItemCount) return; MyGUI::Widget* dragArea = mScrollView->getChildAt(0); - if (_prevFocus >= 0 && _prevFocus < mItemCount) + if (prevFocus >= 0 && prevFocus < mItemCount) { - ItemWidget* prev = (ItemWidget *)dragArea->getChildAt(_prevFocus); + ItemWidget* prev = (ItemWidget *)dragArea->getChildAt(prevFocus); if (prev) prev->setControllerFocus(false); } - if (_newFocus >= 0 && _newFocus < mItemCount) + if (mControllerActiveWindow && newFocus >= 0 && newFocus < mItemCount) { - ItemWidget* focused = (ItemWidget *)dragArea->getChildAt(_newFocus); + ItemWidget* focused = (ItemWidget *)dragArea->getChildAt(newFocus); if (focused) + { focused->setControllerFocus(true); + if (mControllerTooltip) + MWBase::Environment::get().getInputManager()->warpMouseToWidget(focused); + } } } } diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index b1fa941ef4..091436ab05 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -35,7 +35,7 @@ namespace MWGui void setActiveControllerWindow(bool active); int getControllerFocus() { return mControllerFocus; } int getItemCount() { return mItemCount; } - void onControllerButtonEvent(const SDL_ControllerButtonEvent& arg); + void onControllerButtonEvent(const unsigned char button); private: void initialiseOverride() override; @@ -55,6 +55,8 @@ namespace MWGui int mItemCount = 0; int mRows; int mControllerFocus = 0; + bool mControllerTooltip; + bool mControllerActiveWindow; void updateControllerFocus(int prevFocus, int newFocus); }; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index bf4bd7644c..ec9c509797 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -832,6 +833,14 @@ namespace MWGui mGlobalMap->setVisible(global); mLocalMap->setVisible(!global); + + if (Settings::gui().mControllerMenus) + { + mControllerButtons.b = "#{sBack}"; + mControllerButtons.x = global ? "#{sLocal}" : "#{sWorld}"; + mControllerButtons.y = "#{sCenter}"; + mControllerButtons.rStick = "#{sMove}"; + } } void MapWindow::onNoteEditOk() @@ -1208,6 +1217,8 @@ namespace MWGui mLocalMap->setVisible(!global); mButton->setCaptionWithReplacing(global ? "#{sLocal}" : "#{sWorld}"); + mControllerButtons.x = global ? "#{sLocal}" : "#{sWorld}"; + MWBase::Environment::get().getWindowManager()->updateControllerButtonsOverlay(); } void MapWindow::onPinToggled() @@ -1368,6 +1379,49 @@ 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); + else if (arg.button == SDL_CONTROLLER_BUTTON_Y) + centerView(); + + return true; + } + + bool MapWindow::onControllerThumbstickEvent(const SDL_ControllerAxisEvent& arg) + { + int dx = arg.axis == SDL_CONTROLLER_AXIS_RIGHTX ? -10.0f * arg.value / 32767 : 0; + int dy = arg.axis == SDL_CONTROLLER_AXIS_RIGHTY ? -10.0f * arg.value / 32767 : 0; + if (dx == 0 && dy == 0) + return true; + else 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)); + } + return true; + } + + void MapWindow::setActiveControllerWindow(bool active) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::Window* window = mMainWidget->castType(); + window->setCoord(0, active ? 0 : viewSize.height + 1, viewSize.width, viewSize.height - 48); + + WindowBase::setActiveControllerWindow(active); + } + // ------------------------------------------------------------------- EditNoteDialog::EditNoteDialog() diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index ed070c5407..31fe971c60 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -265,6 +265,11 @@ namespace MWGui std::string_view getWindowIdForLua() const override { return "Map"; } + protected: + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; + bool onControllerThumbstickEvent(const SDL_ControllerAxisEvent& 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); diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 678f6ffe1f..739699f35c 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -9,6 +9,9 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/inputmanager.hpp" + #include "tooltips.hpp" namespace MWGui @@ -88,6 +91,9 @@ namespace MWGui const int spellHeight = Settings::gui().mFontSize + 2; mLines.clear(); + mButtons.clear(); + mGroupIndices.clear(); + mControllerTooltip = false; while (mScrollView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); @@ -115,6 +121,7 @@ namespace MWGui t->setCaption(spell.mName + captionSuffix); t->setTextAlign(MyGUI::Align::Left); adjustSpellWidget(spell, i, t); + mButtons.emplace_back(t); if (!spell.mCostColumn.empty() && mShowCostColumn) { @@ -256,6 +263,8 @@ namespace MWGui } else mLines.emplace_back(groupWidget, (MyGUI::Widget*)nullptr, NoSpellIndex); + + mGroupIndices.push_back(mButtons.size()); } void SpellView::setSize(const MyGUI::IntSize& _value) @@ -316,4 +325,87 @@ namespace MWGui { mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); } + + void SpellView::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (mButtons.empty()) + return; + + int prevFocus = mControllerFocus; + + if (arg.button == SDL_CONTROLLER_BUTTON_A) + { + // Select the focused item, if any. + if (mControllerFocus >= 0 && mControllerFocus < mButtons.size()) + onSpellSelected(mButtons.at(mControllerFocus)); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSTICK) + { + // Toggle info tooltip + mControllerTooltip = !mControllerTooltip; + if (mControllerTooltip && mControllerFocus >= 0 && mControllerFocus < mButtons.size()) + MWBase::Environment::get().getInputManager()->warpMouseToWidget(mButtons.at(mControllerFocus)); + } + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_UP) + mControllerFocus--; + else if (arg.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) + mControllerFocus++; + else if (arg.button == 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; + } + else if (arg.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) + { + // Jump to first item in next group + for (int groupIndex : mGroupIndices) + { + if (groupIndex > mControllerFocus) + { + mControllerFocus = groupIndex; + break; + } + } + } + + if (mControllerFocus < 0) + mControllerFocus = mButtons.size() - 1; + else if (mControllerFocus >= mButtons.size()) + mControllerFocus = 0; + + if (prevFocus != mControllerFocus) + updateControllerFocus(prevFocus, mControllerFocus); + } + + void SpellView::updateControllerFocus(int prevFocus, int newFocus) + { + if (mButtons.empty()) + return; + + if (prevFocus >= 0 && prevFocus < mButtons.size()) + { + Gui::SharedStateButton* prev = mButtons.at(prevFocus); + if (prev) + prev->onMouseLostFocus(nullptr); + } + + if (newFocus >= 0 && newFocus < mButtons.size()) + { + Gui::SharedStateButton* focused = mButtons.at(newFocus); + if (focused) + { + focused->onMouseSetFocus(nullptr); + if (mControllerTooltip) + MWBase::Environment::get().getInputManager()->warpMouseToWidget(focused); + } + } + } } diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp index caff43a33e..cf3a43354f 100644 --- a/apps/openmw/mwgui/spellview.hpp +++ b/apps/openmw/mwgui/spellview.hpp @@ -5,6 +5,9 @@ #include #include +#include + +#include #include "spellmodel.hpp" @@ -54,6 +57,8 @@ namespace MWGui void resetScrollbars(); + void onControllerButtonEvent(const SDL_ControllerButtonEvent& arg); + private: MyGUI::ScrollView* mScrollView; @@ -89,6 +94,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 + std::vector mButtons; + /// Keep a list of group offsets for controller navigation + std::vector mGroupIndices; + + int mControllerFocus; + bool mControllerTooltip; + 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 d183a00273..63ba83a895 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -55,6 +57,13 @@ 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) + { + mControllerButtons.a = "#{sSelect}"; + mControllerButtons.b = "#{sBack}"; + mControllerButtons.r3 = "#{sInfo}"; + } } void SpellWindow::onPinToggled() @@ -259,4 +268,23 @@ namespace MWGui else onSpellSelected(spell.mId); } + + bool SpellWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_B) + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + else + mSpellView->onControllerButtonEvent(arg); + + return true; + } + + void SpellWindow::setActiveControllerWindow(bool active) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::Window* window = mMainWidget->castType(); + window->setCoord(0, active ? 0 : viewSize.height + 1, viewSize.width, viewSize.height - 48); + + 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..cc5c934662 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -80,6 +81,11 @@ namespace MWGui MyGUI::Window* t = mMainWidget->castType(); t->eventWindowChangeCoord += MyGUI::newDelegate(this, &StatsWindow::onWindowResize); + if (Settings::gui().mControllerMenus) + { + mControllerButtons.b = "#{sBack}"; + } + onWindowResize(t); } @@ -723,4 +729,23 @@ namespace MWGui else if (!mPinned) MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); } + + bool StatsWindow::onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) + { + if (arg.button == SDL_CONTROLLER_BUTTON_B) + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + + return true; + } + + void StatsWindow::setActiveControllerWindow(bool active) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::Window* window = mMainWidget->castType(); + window->setCoord(0, active ? 0 : viewSize.height + 1, viewSize.width, viewSize.height - 48); + + onWindowResize(window); + + WindowBase::setActiveControllerWindow(active); + } } diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index a3fc3157c5..3021873aa8 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -47,6 +47,10 @@ namespace MWGui std::string_view getWindowIdForLua() const override { return "Stats"; } + protected: + bool onControllerButtonEvent(const SDL_ControllerButtonEvent& arg) override; + void setActiveControllerWindow(bool active) override; + private: void addSkills(const std::vector& skills, const std::string& titleId, const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 2fefe8f249..ac58f1e589 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -896,23 +896,13 @@ namespace MWGui return state.mWindows[activeIndex]; } - else - { - // return pinned windows if visible - // REMOVEME - Log(Debug::Error) << "getTopWindow: " << mGuiModeStates[GM_Inventory].mWindows.size() << " pinned windows"; - for (WindowBase* window : mGuiModeStates[GM_Inventory].mWindows) - if (window->isVisible()) - return window; - else - Log(Debug::Error) << "-- Skipping hidden window " << window; - } + return nullptr; } void WindowManager::cycleActiveControllerWindow(bool next) { - if (mGuiModes.empty()) + if (!Settings::gui().mControllerMenus || mGuiModes.empty()) return; GuiMode mode = mGuiModes.back(); @@ -1380,13 +1370,6 @@ namespace MWGui { for (WindowBase* window : mGuiModeStates[mode].mWindows) window->setPtr(arg); - - // Activate first visible window - mActiveControllerWindows[mode] = -1; - cycleActiveControllerWindow(true); - - // REMOVEME - Log(Debug::Error) << "pushGuiMode: mode=" << mode << ", activeIndex=" << mActiveControllerWindows[mode]; } catch (...) { @@ -1398,6 +1381,13 @@ namespace MWGui updateVisible(); MWBase::Environment::get().getLuaManager()->uiModeChanged(arg); + + if (Settings::gui().mControllerMenus) + { + // Activate first visible window. This needs to be called after updateVisible. + mActiveControllerWindows[mode] = std::max(mActiveControllerWindows[mode] - 1, -1); + cycleActiveControllerWindow(true); + } } void WindowManager::setCullMask(uint32_t mask) @@ -1452,6 +1442,15 @@ namespace MWGui // To make sure that console window get focus again if (mConsole && mConsole->isVisible()) mConsole->onOpen(); + + if (Settings::gui().mControllerMenus && !mGuiModes.empty()) + { + // Re-apply any controller-specific window changes. + const GuiMode mode = mGuiModes.back(); + int winCount = mGuiModeStates[mode].mWindows.size(); + for (int i = 0; i < winCount; i++) + mGuiModeStates[mode].mWindows[i]->setActiveControllerWindow(i == mActiveControllerWindows[mode]); + } } void WindowManager::removeGuiMode(GuiMode mode) @@ -1577,6 +1576,10 @@ namespace MWGui mConsole->executeFile(path); } + std::vector WindowManager::getGuiModeWindows(GuiMode mode) + { + return mGuiModeStates[mode].mWindows; + } MWGui::InventoryWindow* WindowManager::getInventoryWindow() { return mInventoryWindow; @@ -1589,6 +1592,10 @@ namespace MWGui { return mConfirmationDialog; } + MWGui::HUD* WindowManager::getHud() + { + return mHud; + } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; @@ -2119,20 +2126,22 @@ namespace MWGui void WindowManager::updatePinnedWindows() { mInventoryWindow->setPinned(Settings::windows().mInventoryPin); - if (Settings::windows().mInventoryHidden) - mShown = (GuiWindow)(mShown ^ GW_Inventory); - mMap->setPinned(Settings::windows().mMapPin); - if (Settings::windows().mMapHidden) - mShown = (GuiWindow)(mShown ^ GW_Map); - mSpellWindow->setPinned(Settings::windows().mSpellsPin); - if (Settings::windows().mSpellsHidden) - mShown = (GuiWindow)(mShown ^ GW_Magic); - mStatsWindow->setPinned(Settings::windows().mStatsPin); - if (Settings::windows().mStatsHidden) - mShown = (GuiWindow)(mShown ^ GW_Stats); + + // Hide hidden inventory windows, but not in controller mode. + if (!Settings::gui().mControllerMenus) + { + if (Settings::windows().mInventoryHidden) + mShown = (GuiWindow)(mShown ^ GW_Inventory); + if (Settings::windows().mMapHidden) + mShown = (GuiWindow)(mShown ^ GW_Map); + if (Settings::windows().mSpellsHidden) + mShown = (GuiWindow)(mShown ^ GW_Magic); + if (Settings::windows().mStatsHidden) + mShown = (GuiWindow)(mShown ^ GW_Stats); + } } void WindowManager::pinWindow(GuiWindow window) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5dbf3c4689..54695e8234 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -185,7 +185,9 @@ namespace MWGui MWGui::CountDialog* getCountDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; + MWGui::HUD* getHud() override; MWGui::PostProcessorHud* getPostProcessorHud() override; + std::vector getGuiModeWindows(GuiMode mode) override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 328757a954..12d56a3321 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -189,6 +189,13 @@ namespace MWInput return mMouseManager->getMouseMoveY(); } + void InputManager::warpMouseToWidget(MyGUI::Widget* widget) + { + mMouseManager->warpMouseToWidget(widget); + mMouseManager->injectMouseMove(1, 0, 0); + MWBase::Environment::get().getWindowManager()->setCursorActive(true); + } + const std::initializer_list& InputManager::getActionKeySorting() { return mBindingsManager->getActionKeySorting(); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 39a1133db5..b2899e6831 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -81,6 +81,7 @@ namespace MWInput float getControllerAxisValue(SDL_GameControllerAxis axis) const override; int getMouseMoveX() const override; int getMouseMoveY() const override; + void warpMouseToWidget(MyGUI::Widget* widget) override; int getNumActions() override { return A_Last; } const std::initializer_list& getActionKeySorting() override; diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 33dd70c763..1cf7364bf0 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -21,13 +21,14 @@ namespace Gui public: SharedStateButton(); + void onMouseSetFocus(MyGUI::Widget* _old) override; + void onMouseLostFocus(MyGUI::Widget* _new) override; + protected: void updateButtonState(); void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) override; void onMouseButtonReleased(int _left, int _top, MyGUI::MouseButton _id) override; - void onMouseSetFocus(MyGUI::Widget* _old) override; - void onMouseLostFocus(MyGUI::Widget* _new) override; void baseUpdateEnable() override; void shutdownOverride() override;