From f17ea8db983b39fcacbdca12d33fade7c3027fce Mon Sep 17 00:00:00 2001 From: staphen Date: Wed, 23 Nov 2022 13:59:36 -0500 Subject: [PATCH] Implement settings menu for gamepad customization --- CMake/Assets.cmake | 2 + Packaging/resources/assets/fonts/24-e0.bin | Bin 0 -> 256 bytes Packaging/resources/assets/fonts/24-e0.clx | Bin 0 -> 21625 bytes Source/DiabloUI/settingsmenu.cpp | 122 ++++++++++++- Source/controls/controller_buttons.cpp | 190 +++++++++++++++++++- Source/controls/controller_buttons.h | 15 +- Source/controls/devices/game_controller.cpp | 6 +- Source/options.cpp | 46 ++--- Source/options.h | 6 +- 9 files changed, 345 insertions(+), 42 deletions(-) create mode 100644 Packaging/resources/assets/fonts/24-e0.bin create mode 100644 Packaging/resources/assets/fonts/24-e0.clx diff --git a/CMake/Assets.cmake b/CMake/Assets.cmake index 5033dea10..c791c032d 100644 --- a/CMake/Assets.cmake +++ b/CMake/Assets.cmake @@ -111,6 +111,8 @@ set(devilutionx_assets fonts/24-20.bin fonts/24-20.clx fonts/24-26.clx + fonts/24-e0.bin + fonts/24-e0.clx fonts/30-00.bin fonts/30-00.clx fonts/30-01.bin diff --git a/Packaging/resources/assets/fonts/24-e0.bin b/Packaging/resources/assets/fonts/24-e0.bin new file mode 100644 index 0000000000000000000000000000000000000000..aa83226a2a212ab3e11a2a6662f19499ff1abf3f GIT binary patch literal 256 zcmd<%ikSs_Q4-eQN9IE&j1|a|d=+_5` literal 0 HcmV?d00001 diff --git a/Packaging/resources/assets/fonts/24-e0.clx b/Packaging/resources/assets/fonts/24-e0.clx new file mode 100644 index 0000000000000000000000000000000000000000..41ac5c4b33075009ae01b1acdb34646152ef9341 GIT binary patch literal 21625 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 DK|zz$ literal 0 HcmV?d00001 diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index 70a0a749d..88c9fd3b5 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/Source/DiabloUI/settingsmenu.cpp @@ -5,6 +5,8 @@ #include "DiabloUI/diabloui.h" #include "DiabloUI/scrollbar.h" #include "control.h" +#include "controls/controller_motion.h" +#include "controls/plrctrls.h" #include "controls/remap_keyboard.h" #include "engine/render/text_render.hpp" #include "hwcursor.hpp" @@ -17,7 +19,8 @@ namespace devilution { namespace { -constexpr size_t IndexKeyInput = 1; +constexpr size_t IndexKeyOrPadInput = 1; +constexpr size_t IndexPadTimerText = 2; bool endMenu = false; bool backToMain = false; @@ -31,6 +34,7 @@ enum class ShownMenuType : uint8_t { Settings, ListOption, KeyInput, + PadInput, }; ShownMenuType shownMenu; @@ -44,8 +48,14 @@ enum class SpecialMenuEntry : int8_t { None = -1, PreviousMenu = -2, UnbindKey = -3, + BindPadButton = -4, + UnbindPadButton = -5, }; +ControllerButtonCombo padEntryCombo {}; +Uint32 padEntryStartTime = 0; +std::string padEntryTimerText; + bool IsValidEntry(OptionEntryBase *pOptionEntry) { auto flags = pOptionEntry->GetFlags(); @@ -90,6 +100,41 @@ void GoBackOneMenuLevel() shownMenu = ShownMenuType::Settings; } +void StartPadEntryTimer() +{ + padEntryCombo = ControllerButton_NONE; + padEntryStartTime = SDL_GetTicks(); + if (padEntryStartTime == 0) + padEntryStartTime++; + // Removes access to these dialog items while entering bindings + for (size_t i = IndexPadTimerText + 1; i < vecDialogItems.size(); i++) + vecDialogItems[i]->uiFlags |= UiFlags::ElementHidden; +} + +void StopPadEntryTimer() +{ + padEntryCombo = ControllerButton_NONE; + padEntryStartTime = 0; + padEntryTimerText = ""; + vecDialogItems[IndexPadTimerText]->m_text = padEntryTimerText; + // Restores access to these dialog items after binding is complete + for (size_t i = IndexPadTimerText + 1; i < vecDialogItems.size(); i++) + vecDialogItems[i]->uiFlags &= ~UiFlags::ElementHidden; +} + +void UpdatePadEntryTimerText() +{ + if (shownMenu != ShownMenuType::PadInput) + return; + Uint32 elapsed = SDL_GetTicks() - padEntryStartTime; + if (padEntryStartTime == 0 || elapsed > 10000) { + StopPadEntryTimer(); + return; + } + padEntryTimerText = StrCat(_("Press gamepad buttons to change."), " ", 10 - elapsed / 1000); + vecDialogItems[IndexPadTimerText]->m_text = padEntryTimerText; +} + void UpdateDescription(const OptionEntryBase &option) { auto paragraphs = WordWrapString(option.GetDescription(), rectDescription.size.width, GameFont12, 1); @@ -159,10 +204,19 @@ void ItemSelected(int value) case SpecialMenuEntry::PreviousMenu: GoBackOneMenuLevel(); break; - case SpecialMenuEntry::UnbindKey: + case SpecialMenuEntry::UnbindKey: { auto *pOptionKey = static_cast(selectedOption); pOptionKey->SetValue(SDLK_UNKNOWN); - vecDialogItems[IndexKeyInput]->m_text = selectedOption->GetValueDescription().data(); + vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription().data(); + break; + } + case SpecialMenuEntry::BindPadButton: + StartPadEntryTimer(); + break; + case SpecialMenuEntry::UnbindPadButton: + auto *pOptionPad = static_cast(selectedOption); + pOptionPad->SetValue(ControllerButton_NONE); + vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription().data(); break; } return; @@ -189,6 +243,10 @@ void ItemSelected(int value) selectedOption = pOption; endMenu = true; shownMenu = ShownMenuType::KeyInput; + } else if (pOption->GetType() == OptionEntryType::PadButton) { + selectedOption = pOption; + endMenu = true; + shownMenu = ShownMenuType::PadInput; } else { updateValueDescription = ChangeOptionValue(pOption, 0); } @@ -213,6 +271,7 @@ void ItemSelected(int value) GoBackOneMenuLevel(); } break; case ShownMenuType::KeyInput: + case ShownMenuType::PadInput: break; } } @@ -312,10 +371,10 @@ void UiSettingsMenu() case ShownMenuType::KeyInput: { vecDialogItems.push_back(std::make_unique(_("Bound key:"), static_cast(SpecialMenuEntry::None), UiFlags::ColorWhitegold | UiFlags::ElementDisabled)); vecDialogItems.push_back(std::make_unique(selectedOption->GetValueDescription(), static_cast(SpecialMenuEntry::None), UiFlags::ColorUiGold)); - assert(IndexKeyInput == vecDialogItems.size() - 1); - itemToSelect = IndexKeyInput; + assert(IndexKeyOrPadInput == vecDialogItems.size() - 1); + itemToSelect = IndexKeyOrPadInput; eventHandler = [](SDL_Event &event) { - if (SelectedItem != IndexKeyInput) + if (SelectedItem != IndexKeyOrPadInput) return false; uint32_t key = SDLK_UNKNOWN; switch (event.type) { @@ -343,7 +402,7 @@ void UiSettingsMenu() auto *pOptionKey = static_cast(selectedOption); if (!pOptionKey->SetValue(key)) return false; - vecDialogItems[IndexKeyInput]->m_text = selectedOption->GetValueDescription().data(); + vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription().data(); return true; }; vecDialogItems.push_back(std::make_unique(_("Press any key to change."), static_cast(SpecialMenuEntry::None), UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); @@ -351,6 +410,53 @@ void UiSettingsMenu() vecDialogItems.push_back(std::make_unique(_("Unbind key"), static_cast(SpecialMenuEntry::UnbindKey), UiFlags::ColorUiGold)); UpdateDescription(*selectedOption); } break; + case ShownMenuType::PadInput: { + vecDialogItems.push_back(std::make_unique(_("Bound button combo:"), static_cast(SpecialMenuEntry::None), UiFlags::ColorWhitegold | UiFlags::ElementDisabled)); + vecDialogItems.push_back(std::make_unique(selectedOption->GetValueDescription(), static_cast(SpecialMenuEntry::BindPadButton), UiFlags::ColorUiGold)); + assert(IndexKeyOrPadInput == vecDialogItems.size() - 1); + itemToSelect = IndexKeyOrPadInput; + + vecDialogItems.push_back(std::make_unique(padEntryTimerText, static_cast(SpecialMenuEntry::None), UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); + assert(IndexPadTimerText == vecDialogItems.size() - 1); + + vecDialogItems.push_back(std::make_unique("", static_cast(SpecialMenuEntry::None), UiFlags::ElementDisabled)); + vecDialogItems.push_back(std::make_unique(_("Unbind button combo"), static_cast(SpecialMenuEntry::UnbindPadButton), UiFlags::ColorUiGold)); + + padEntryStartTime = 0; + eventHandler = [](SDL_Event &event) { + if (padEntryStartTime == 0) + return false; + + ControllerButtonEvent ctrlEvent = ToControllerButtonEvent(event); + bool isGamepadMotion = ProcessControllerMotion(event, ctrlEvent); + DetectInputMethod(event, ctrlEvent); + if (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE) { + StopPadEntryTimer(); + return true; + } + if (isGamepadMotion || IsAnyOf(ctrlEvent.button, ControllerButton_NONE, ControllerButton_IGNORE)) { + return true; + } + + bool modifierPressed = padEntryCombo.modifier != ControllerButton_NONE && IsControllerButtonPressed(padEntryCombo.modifier); + bool buttonPressed = padEntryCombo.button != ControllerButton_NONE && IsControllerButtonPressed(padEntryCombo.button); + if (ctrlEvent.up) { + // When the player has released all relevant inputs, assume the binding is finished and stop the timer + if (padEntryCombo.button != ControllerButton_NONE && !modifierPressed && !buttonPressed) + StopPadEntryTimer(); + return true; + } + + auto *pOptionPad = static_cast(selectedOption); + if (!modifierPressed && buttonPressed) + padEntryCombo.modifier = padEntryCombo.button; + padEntryCombo.button = ctrlEvent.button; + if (pOptionPad->SetValue(padEntryCombo)) + vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription().data(); + return true; + }; + UpdateDescription(*selectedOption); + } break; } vecDialogItems.push_back(std::make_unique("", static_cast(SpecialMenuEntry::None), UiFlags::ElementDisabled)); @@ -362,7 +468,7 @@ void UiSettingsMenu() while (!endMenu) { UiClearScreen(); - UiRenderItems(vecDialog); + UpdatePadEntryTimerText(); UiPollAndRender(eventHandler); } diff --git a/Source/controls/controller_buttons.cpp b/Source/controls/controller_buttons.cpp index 69299ee70..4da80c5be 100644 --- a/Source/controls/controller_buttons.cpp +++ b/Source/controls/controller_buttons.cpp @@ -1,5 +1,7 @@ #include "controller_buttons.h" +#include "plrctrls.h" + namespace devilution { namespace controller_button_icon { const string_view Playstation_Triangle = "\uE000"; @@ -56,7 +58,7 @@ const string_view Nintendo_LStick_W = "\uE032"; const string_view Nintendo_LStick_SW = "\uE033"; const string_view Nintendo_LStick_N = "\uE034"; const string_view Nintendo_LStick = "\uE035"; -const string_view Nintendoo_LStick_S = "\uE036"; +const string_view Nintendo_LStick_S = "\uE036"; const string_view Nintendo_LStick_NE = "\uE037"; const string_view Nintendo_LStick_E = "\uE038"; const string_view Nintendo_LStick_SE = "\uE039"; @@ -86,9 +88,9 @@ const string_view Xbox_RT = "\uE050"; const string_view Xbox_LB = "\uE051"; const string_view Xbox_RB = "\uE052"; const string_view Xbox_DPad_Up = "\uE053"; -const string_view Xbox_Dpad_Right = "\uE054"; -const string_view Xbox_Dpad_Down = "\uE055"; -const string_view Xbox_Dpad_Left = "\uE056"; +const string_view Xbox_DPad_Right = "\uE054"; +const string_view Xbox_DPad_Down = "\uE055"; +const string_view Xbox_DPad_Left = "\uE056"; const string_view Xbox_LStick_NW = "\uE057"; const string_view Xbox_LStick_W = "\uE058"; const string_view Xbox_LStick_SW = "\uE059"; @@ -110,4 +112,184 @@ const string_view Xbox_RStick_SE = "\uE068"; const string_view Xbox_RStick_Click = "\uE069"; const string_view Xbox_Xbox = "\uE06A"; } // namespace controller_button_icon + +string_view ToPlayStationIcon(ControllerButton button) +{ + switch (button) { + case devilution::ControllerButton_BUTTON_A: + return controller_button_icon::Playstation_X; + case devilution::ControllerButton_BUTTON_B: + return controller_button_icon::Playstation_Circle; + case devilution::ControllerButton_BUTTON_X: + return controller_button_icon::Playstation_Square; + case devilution::ControllerButton_BUTTON_Y: + return controller_button_icon::Playstation_Triangle; + case devilution::ControllerButton_BUTTON_START: + return controller_button_icon::Playstation_Options; + case devilution::ControllerButton_BUTTON_BACK: + return controller_button_icon::Playstation_Share; + case devilution::ControllerButton_AXIS_TRIGGERLEFT: + return controller_button_icon::Playstation_L2; + case devilution::ControllerButton_AXIS_TRIGGERRIGHT: + return controller_button_icon::Playstation_R2; + case devilution::ControllerButton_BUTTON_LEFTSHOULDER: + return controller_button_icon::Playstation_L1; + case devilution::ControllerButton_BUTTON_RIGHTSHOULDER: + return controller_button_icon::Playstation_R1; + case devilution::ControllerButton_BUTTON_LEFTSTICK: + return controller_button_icon::Playstation_L3; + case devilution::ControllerButton_BUTTON_RIGHTSTICK: + return controller_button_icon::Playstation_R3; + case devilution::ControllerButton_BUTTON_DPAD_UP: + return controller_button_icon::Playstation_DPad_Up; + case devilution::ControllerButton_BUTTON_DPAD_DOWN: + return controller_button_icon::Playstation_DPad_Down; + case devilution::ControllerButton_BUTTON_DPAD_LEFT: + return controller_button_icon::Playstation_DPad_Left; + case devilution::ControllerButton_BUTTON_DPAD_RIGHT: + return controller_button_icon::Playstation_DPad_Right; + default: + return ToGenericButtonText(button); + } +} + +string_view ToNintendoIcon(ControllerButton button) +{ + switch (button) { + case devilution::ControllerButton_BUTTON_A: + return controller_button_icon::Nintendo_B; + case devilution::ControllerButton_BUTTON_B: + return controller_button_icon::Nintendo_A; + case devilution::ControllerButton_BUTTON_X: + return controller_button_icon::Nintendo_Y; + case devilution::ControllerButton_BUTTON_Y: + return controller_button_icon::Nintendo_X; + case devilution::ControllerButton_BUTTON_START: + return controller_button_icon::Nintendo_Plus; + case devilution::ControllerButton_BUTTON_BACK: + return controller_button_icon::Nintendo_Minus; + case devilution::ControllerButton_AXIS_TRIGGERLEFT: + return controller_button_icon::Nintendo_ZL; + case devilution::ControllerButton_AXIS_TRIGGERRIGHT: + return controller_button_icon::Nintendo_ZR; + case devilution::ControllerButton_BUTTON_LEFTSHOULDER: + return controller_button_icon::Nintendo_L; + case devilution::ControllerButton_BUTTON_RIGHTSHOULDER: + return controller_button_icon::Nintendo_R; + case devilution::ControllerButton_BUTTON_LEFTSTICK: + return controller_button_icon::Nintendo_LStick_Click; + case devilution::ControllerButton_BUTTON_RIGHTSTICK: + return controller_button_icon::Nintendo_RStick_Click; + case devilution::ControllerButton_BUTTON_DPAD_UP: + return controller_button_icon::Nintendo_DPad_Up; + case devilution::ControllerButton_BUTTON_DPAD_DOWN: + return controller_button_icon::Nintendo_DPad_Down; + case devilution::ControllerButton_BUTTON_DPAD_LEFT: + return controller_button_icon::Nintendo_DPad_Left; + case devilution::ControllerButton_BUTTON_DPAD_RIGHT: + return controller_button_icon::Nintendo_DPad_Right; + default: + return ToGenericButtonText(button); + } +} + +string_view ToXboxIcon(ControllerButton button) +{ + switch (button) { + case devilution::ControllerButton_BUTTON_A: + return controller_button_icon::Xbox_A; + case devilution::ControllerButton_BUTTON_B: + return controller_button_icon::Xbox_B; + case devilution::ControllerButton_BUTTON_X: + return controller_button_icon::Xbox_X; + case devilution::ControllerButton_BUTTON_Y: + return controller_button_icon::Xbox_Y; + case devilution::ControllerButton_BUTTON_START: + return controller_button_icon::Xbox_Menu; + case devilution::ControllerButton_BUTTON_BACK: + return controller_button_icon::Xbox_View; + case devilution::ControllerButton_AXIS_TRIGGERLEFT: + return controller_button_icon::Xbox_LT; + case devilution::ControllerButton_AXIS_TRIGGERRIGHT: + return controller_button_icon::Xbox_RT; + case devilution::ControllerButton_BUTTON_LEFTSHOULDER: + return controller_button_icon::Xbox_LB; + case devilution::ControllerButton_BUTTON_RIGHTSHOULDER: + return controller_button_icon::Xbox_RB; + case devilution::ControllerButton_BUTTON_LEFTSTICK: + return controller_button_icon::Xbox_LStick_Click; + case devilution::ControllerButton_BUTTON_RIGHTSTICK: + return controller_button_icon::Xbox_RStick_Click; + case devilution::ControllerButton_BUTTON_DPAD_UP: + return controller_button_icon::Xbox_DPad_Up; + case devilution::ControllerButton_BUTTON_DPAD_DOWN: + return controller_button_icon::Xbox_DPad_Down; + case devilution::ControllerButton_BUTTON_DPAD_LEFT: + return controller_button_icon::Xbox_DPad_Left; + case devilution::ControllerButton_BUTTON_DPAD_RIGHT: + return controller_button_icon::Xbox_DPad_Right; + default: + return ToGenericButtonText(button); + } +} + +string_view ToGenericButtonText(ControllerButton button) +{ + switch (button) { + case devilution::ControllerButton_BUTTON_A: + return "A"; + case devilution::ControllerButton_BUTTON_B: + return "B"; + case devilution::ControllerButton_BUTTON_X: + return "X"; + case devilution::ControllerButton_BUTTON_Y: + return "Y"; + case devilution::ControllerButton_BUTTON_START: + return "Start"; + case devilution::ControllerButton_BUTTON_BACK: + return "Select"; + case devilution::ControllerButton_AXIS_TRIGGERLEFT: + return "LT"; + case devilution::ControllerButton_AXIS_TRIGGERRIGHT: + return "RT"; + case devilution::ControllerButton_BUTTON_LEFTSHOULDER: + return "LB"; + case devilution::ControllerButton_BUTTON_RIGHTSHOULDER: + return "RB"; + case devilution::ControllerButton_BUTTON_LEFTSTICK: + return "LS"; + case devilution::ControllerButton_BUTTON_RIGHTSTICK: + return "RS"; + case devilution::ControllerButton_BUTTON_DPAD_UP: + return "Up"; + case devilution::ControllerButton_BUTTON_DPAD_DOWN: + return "Down"; + case devilution::ControllerButton_BUTTON_DPAD_LEFT: + return "Left"; + case devilution::ControllerButton_BUTTON_DPAD_RIGHT: + return "Right"; + case devilution::ControllerButton_NONE: + return "None"; + case devilution::ControllerButton_IGNORE: + return "Ignored"; + default: + return "Unknown"; + } +} + +string_view ToString(ControllerButton button) +{ + switch (GamepadType) { + case devilution::GamepadLayout::PlayStation: + return ToPlayStationIcon(button); + case devilution::GamepadLayout::Nintendo: + return ToNintendoIcon(button); + case devilution::GamepadLayout::Xbox: + return ToXboxIcon(button); + default: + case devilution::GamepadLayout::Generic: + return ToGenericButtonText(button); + } +} + } // namespace devilution diff --git a/Source/controls/controller_buttons.h b/Source/controls/controller_buttons.h index 2dc2ef676..a65bee5dd 100644 --- a/Source/controls/controller_buttons.h +++ b/Source/controls/controller_buttons.h @@ -119,7 +119,7 @@ extern const string_view Nintendo_LStick_W; extern const string_view Nintendo_LStick_SW; extern const string_view Nintendo_LStick_N; extern const string_view Nintendo_LStick; -extern const string_view Nintendoo_LStick_S; +extern const string_view Nintendo_LStick_S; extern const string_view Nintendo_LStick_NE; extern const string_view Nintendo_LStick_E; extern const string_view Nintendo_LStick_SE; @@ -149,9 +149,9 @@ extern const string_view Xbox_RT; extern const string_view Xbox_LB; extern const string_view Xbox_RB; extern const string_view Xbox_DPad_Up; -extern const string_view Xbox_Dpad_Right; -extern const string_view Xbox_Dpad_Down; -extern const string_view Xbox_Dpad_Left; +extern const string_view Xbox_DPad_Right; +extern const string_view Xbox_DPad_Down; +extern const string_view Xbox_DPad_Left; extern const string_view Xbox_LStick_NW; extern const string_view Xbox_LStick_W; extern const string_view Xbox_LStick_SW; @@ -173,4 +173,11 @@ extern const string_view Xbox_RStick_SE; extern const string_view Xbox_RStick_Click; extern const string_view Xbox_Xbox; } // namespace controller_button_icon + +string_view ToPlayStationIcon(ControllerButton button); +string_view ToNintendoIcon(ControllerButton button); +string_view ToXboxIcon(ControllerButton button); +string_view ToGenericButtonText(ControllerButton button); +string_view ToString(ControllerButton button); + } // namespace devilution diff --git a/Source/controls/devices/game_controller.cpp b/Source/controls/devices/game_controller.cpp index 060c5f843..f9baaabcd 100644 --- a/Source/controls/devices/game_controller.cpp +++ b/Source/controls/devices/game_controller.cpp @@ -25,8 +25,9 @@ ControllerButton GameController::ToControllerButton(const SDL_Event &event) case SDL_CONTROLLERAXISMOTION: switch (event.caxis.axis) { case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - if (event.caxis.value < 8192) { // 25% pressed + if (event.caxis.value < 8192 && trigger_left_is_down_) { // 25% pressed trigger_left_is_down_ = false; + trigger_left_state_ = ControllerButton_AXIS_TRIGGERLEFT; } if (event.caxis.value > 16384 && !trigger_left_is_down_) { // 50% pressed trigger_left_is_down_ = true; @@ -34,8 +35,9 @@ ControllerButton GameController::ToControllerButton(const SDL_Event &event) } return trigger_left_state_; case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - if (event.caxis.value < 8192) { // 25% pressed + if (event.caxis.value < 8192 && trigger_right_is_down_) { // 25% pressed trigger_right_is_down_ = false; + trigger_right_state_ = ControllerButton_AXIS_TRIGGERRIGHT; } if (event.caxis.value > 16384 && !trigger_right_is_down_) { // 50% pressed trigger_right_is_down_ = true; diff --git a/Source/options.cpp b/Source/options.cpp index bd18aac97..b1392fafa 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -16,6 +16,7 @@ #include "control.h" #include "controls/controller.h" #include "controls/game_controls.h" +#include "controls/plrctrls.h" #include "discord/discord.h" #include "engine/demomode.h" #include "engine/sound_defs.hpp" @@ -1462,7 +1463,7 @@ std::vector PadmapperOptions::GetEntries() } PadmapperOptions::Action::Action(string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index) - : OptionEntryBase(key, OptionEntryFlags::Invisible, name, description) + : OptionEntryBase(key, OptionEntryFlags::None, name, description) , defaultInput(defaultInput) , actionPressed(std::move(actionPressed)) , actionReleased(std::move(actionReleased)) @@ -1553,37 +1554,36 @@ void PadmapperOptions::Action::SaveToIni(string_view category) const SetIniValue(category.data(), key.data(), inputName.data()); } +void PadmapperOptions::Action::UpdateValueDescription() const +{ + boundInputDescriptionType = GamepadType; + if (boundInput.button == ControllerButton_NONE) { + boundInputDescription = ""; + return; + } + string_view buttonName = ToString(boundInput.button); + if (boundInput.modifier == ControllerButton_NONE) { + boundInputDescription = std::string(buttonName); + return; + } + string_view modifierName = ToString(boundInput.modifier); + boundInputDescription = StrCat(modifierName, "+", buttonName); +} + string_view PadmapperOptions::Action::GetValueDescription() const { + if (GamepadType != boundInputDescriptionType) + UpdateValueDescription(); return boundInputDescription; } bool PadmapperOptions::Action::SetValue(ControllerButtonCombo value) { - const std::string &modifierName = sgOptions.Padmapper.buttonToButtonName[static_cast(value.modifier)]; - const std::string &buttonName = sgOptions.Padmapper.buttonToButtonName[static_cast(value.button)]; - if ((value.modifier != ControllerButton_NONE && modifierName.empty()) - || (value.button != ControllerButton_NONE && buttonName.empty())) { - // Ignore invalid button combos - return false; - } - - // Remove old button combo - if (boundInput.button != ControllerButton_NONE) { + if (boundInput.button != ControllerButton_NONE) boundInput = {}; - boundInputDescription = ""; - } - - // Add new button combo - if (value.button != ControllerButton_NONE) { + if (value.button != ControllerButton_NONE) boundInput = value; - boundInputDescription = buttonName; - - if (!modifierName.empty()) { - boundInputDescription = StrCat(modifierName, "+", boundInputDescription); - } - } - + UpdateValueDescription(); return true; } diff --git a/Source/options.h b/Source/options.h index 7091a907e..d5d445db8 100644 --- a/Source/options.h +++ b/Source/options.h @@ -10,6 +10,7 @@ #include "controls/controller.h" #include "controls/controller_buttons.h" +#include "controls/game_controls.h" #include "engine/sound_defs.hpp" #include "miniwin/misc_msg.h" #include "pack.h" @@ -722,11 +723,14 @@ struct PadmapperOptions : OptionCategoryBase { std::function actionReleased; std::function enable; ControllerButtonCombo boundInput {}; - std::string boundInputDescription; + mutable GamepadLayout boundInputDescriptionType = GamepadLayout::Generic; + mutable std::string boundInputDescription; unsigned dynamicIndex; std::string dynamicKey; mutable std::string dynamicName; + void UpdateValueDescription() const; + friend struct PadmapperOptions; };