From d94ea1511df9c11659fdd6570c95c6db530b4690 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sat, 29 Mar 2025 19:44:57 +0000 Subject: [PATCH] FSUI: Add option to use legacy SDL2 Nintendo layout --- pcsx2/ImGui/FullscreenUI.cpp | 107 +++++++++++++++++++++++++++-------- pcsx2/ImGui/ImGuiManager.cpp | 22 ++++++- pcsx2/ImGui/ImGuiManager.h | 6 ++ 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index f41316f4a5..ca7cb3dc0b 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -237,7 +237,7 @@ namespace FullscreenUI static void DrawAboutWindow(); static void OpenAboutWindow(); static void GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel); - static void ApplyConfirmSetting(const SettingsInterface* bsi = nullptr); + static void ApplyLayoutSettings(const SettingsInterface* bsi = nullptr); static MainWindowType s_current_main_window = MainWindowType::None; static PauseSubMenu s_current_pause_submenu = PauseSubMenu::None; @@ -542,10 +542,11 @@ void ImGuiFullscreen::GetFileSelectorHelpText(SmallStringBase& dest) if (IsGamepadInputSource()) { const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; + const bool swapNorthWest = ImGuiManager::IsGamepadNorthWestSwapped(); ImGuiFullscreen::CreateFooterTextString( dest, std::array{ std::make_pair(ICON_PF_DPAD_UP_DOWN, FSUI_VSTR("Change Selection")), - std::make_pair(ICON_PF_BUTTON_TRIANGLE, FSUI_VSTR("Parent Directory")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_SQUARE : ICON_PF_BUTTON_TRIANGLE, FSUI_VSTR("Parent Directory")), std::make_pair(circleOK ? ICON_PF_BUTTON_CIRCLE : ICON_PF_BUTTON_CROSS, FSUI_VSTR("Select")), std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, FSUI_VSTR("Cancel")), }); @@ -584,7 +585,7 @@ void ImGuiFullscreen::GetInputDialogHelpText(SmallStringBase& dest) } } -void FullscreenUI::ApplyConfirmSetting(const SettingsInterface* bsi) +void FullscreenUI::ApplyLayoutSettings(const SettingsInterface* bsi) { ImGuiIO& io = ImGui::GetIO(); SmallString swap_mode; @@ -593,12 +594,43 @@ void FullscreenUI::ApplyConfirmSetting(const SettingsInterface* bsi) else swap_mode = Host::GetBaseSmallStringSettingValue("UI", "SwapOKFullscreenUI", "auto"); + // Check Nintendo Setting + SmallString sdl2_nintendo_mode; + if (bsi) + sdl2_nintendo_mode = bsi->GetSmallStringValue("UI", "SDL2NintendoLayout", "false"); + else + sdl2_nintendo_mode = Host::GetBaseSmallStringSettingValue("UI", "SDL2NintendoLayout", "false"); + + const InputLayout layout = ImGuiFullscreen::GetGamepadLayout(); + + if (sdl2_nintendo_mode == "true" || (sdl2_nintendo_mode == "auto") && layout == InputLayout::Nintendo) + { + // Apply + ImGuiManager::SwapGamepadNorthWest(true); + + // Check swap_mode if A/B should also be swapped + if (swap_mode == "auto") + { + io.ConfigNavSwapGamepadButtons = true; + return; + } + } + else + ImGuiManager::SwapGamepadNorthWest(false); + if (swap_mode == "true") io.ConfigNavSwapGamepadButtons = true; else if (swap_mode == "false") io.ConfigNavSwapGamepadButtons = false; else if (swap_mode == "auto") { + // Check gamepad + if (layout == InputLayout::Nintendo) + { + io.ConfigNavSwapGamepadButtons = true; + return; + } + // Check language if (Host::LocaleCircleConfirm()) { @@ -628,14 +660,6 @@ void FullscreenUI::ApplyConfirmSetting(const SettingsInterface* bsi) } } - // Check gamepad - const InputLayout layout = ImGuiFullscreen::GetGamepadLayout(); - if (layout == InputLayout::Nintendo) - { - io.ConfigNavSwapGamepadButtons = true; - return; - } - // X is confirm io.ConfigNavSwapGamepadButtons = false; return; @@ -647,12 +671,12 @@ void FullscreenUI::ApplyConfirmSetting(const SettingsInterface* bsi) void FullscreenUI::LocaleChanged() { - ApplyConfirmSetting(); + ApplyLayoutSettings(); } void FullscreenUI::GamepadLayoutChanged() { - ApplyConfirmSetting(); + ApplyLayoutSettings(); } ////////////////////////////////////////////////////////////////////////// @@ -669,7 +693,7 @@ bool FullscreenUI::Initialize() ImGuiFullscreen::SetTheme(Host::GetBaseStringSettingValue("UI", "FullscreenUITheme", "Dark")); ImGuiFullscreen::UpdateLayoutScale(); - ApplyConfirmSetting(); + ApplyLayoutSettings(); if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("fullscreenui/placeholder.png") || !LoadResources()) { @@ -740,7 +764,7 @@ void FullscreenUI::CheckForConfigChanges(const Pcsx2Config& old_config) if (old_config.FullpathToBios() != EmuConfig.FullpathToBios()) { MTGS::RunOnGSThread([]() { - ApplyConfirmSetting(); + ApplyLayoutSettings(); }); } } @@ -1405,11 +1429,12 @@ void FullscreenUI::DrawLandingWindow() if (IsGamepadInputSource()) { const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; + const bool swapNorthWest = ImGuiManager::IsGamepadNorthWestSwapped(); SetFullscreenFooterText(std::array{ std::make_pair(ICON_PF_SELECT_SHARE, FSUI_VSTR("About")), std::make_pair(ICON_PF_DPAD_LEFT_RIGHT, FSUI_VSTR("Navigate")), - std::make_pair(ICON_PF_BUTTON_TRIANGLE, FSUI_VSTR("Game List")), - std::make_pair(ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Toggle Fullscreen")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_SQUARE : ICON_PF_BUTTON_TRIANGLE, FSUI_VSTR("Game List")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_TRIANGLE : ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Toggle Fullscreen")), std::make_pair(circleOK ? ICON_PF_BUTTON_CIRCLE : ICON_PF_BUTTON_CROSS, FSUI_VSTR("Select")), std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, FSUI_VSTR("Exit")), }); @@ -1478,9 +1503,10 @@ void FullscreenUI::DrawStartGameWindow() if (IsGamepadInputSource()) { const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; + const bool swapNorthWest = ImGuiManager::IsGamepadNorthWestSwapped(); SetFullscreenFooterText(std::array{ std::make_pair(ICON_PF_DPAD_LEFT_RIGHT, FSUI_VSTR("Navigate")), - std::make_pair(ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Load Global State")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_TRIANGLE : ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Load Global State")), std::make_pair(circleOK ? ICON_PF_BUTTON_CIRCLE : ICON_PF_BUTTON_CROSS, FSUI_VSTR("Select")), std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, FSUI_VSTR("Back")), }); @@ -3418,7 +3444,40 @@ void FullscreenUI::DrawInterfaceSettingsPage() SettingsInterface* bsi = GetEditingSettingsInterface(false); bsi->SetStringValue("UI", "SwapOKFullscreenUI", swap_values[index]); SetSettingsChanged(bsi); - ApplyConfirmSetting(bsi); + ApplyLayoutSettings(bsi); + } + + CloseChoiceDialog(); + }); + } + + const SmallString nintendo_mode = bsi->GetSmallStringValue("UI", "SDL2NintendoLayout", "false"); + size_t nintendo_index = std::size(swap_values); + for (size_t i = 0; i < std::size(swap_values); i++) + { + if (nintendo_mode == swap_values[i]) + { + nintendo_index = i; + break; + } + } + swap_summery.format(FSUI_FSTR("Swaps both {}/{} (When Swap OK/Cancel is set to automatic) and {}/{} buttons"), ICON_PF_BUTTON_CROSS, ICON_PF_BUTTON_CIRCLE, ICON_PF_BUTTON_SQUARE, ICON_PF_BUTTON_TRIANGLE); + if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Use Legacy Nintendo Layout in Big Picture Mode"), swap_summery.c_str(), + (nintendo_index < std::size(swap_values)) ? Host::TranslateToCString(TR_CONTEXT, swap_names[nintendo_index]) : FSUI_CSTR("Unknown"))) + { + ImGuiFullscreen::ChoiceDialogOptions cd_options; + cd_options.reserve(std::size(swap_values)); + for (size_t i = 0; i < std::size(swap_values); i++) + cd_options.emplace_back(Host::TranslateToString(TR_CONTEXT, swap_names[i]), i == static_cast(nintendo_index)); + + OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Use Legacy Nintendo Layout in Big Picture Mode"), false, std::move(cd_options), [](s32 index, const std::string& title, bool checked) { + if (index >= 0) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(false); + bsi->SetStringValue("UI", "SDL2NintendoLayout", swap_values[index]); + SetSettingsChanged(bsi); + ApplyLayoutSettings(bsi); } CloseChoiceDialog(); @@ -3542,7 +3601,7 @@ void FullscreenUI::DrawBIOSSettingsPage() SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); bsi->SetStringValue("Filenames", "BIOS", values[index].c_str()); SetSettingsChanged(bsi); - ApplyConfirmSetting(bsi); + ApplyLayoutSettings(bsi); CloseChoiceDialog(); }); } @@ -5971,9 +6030,10 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) if (IsGamepadInputSource()) { const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; + const bool swapNorthWest = ImGuiManager::IsGamepadNorthWestSwapped(); SetFullscreenFooterText(std::array{ std::make_pair(ICON_PF_DPAD, FSUI_VSTR("Select State")), - std::make_pair(ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Options")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_TRIANGLE : ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Options")), std::make_pair(circleOK ? ICON_PF_BUTTON_CIRCLE : ICON_PF_BUTTON_CROSS, FSUI_VSTR("Load/Save State")), std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, FSUI_VSTR("Cancel")), }); @@ -6270,11 +6330,12 @@ void FullscreenUI::DrawGameListWindow() if (IsGamepadInputSource()) { const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons; + const bool swapNorthWest = ImGuiManager::IsGamepadNorthWestSwapped(); SetFullscreenFooterText(std::array{ std::make_pair(ICON_PF_DPAD, FSUI_VSTR("Select Game")), std::make_pair(ICON_PF_START, FSUI_VSTR("Settings")), - std::make_pair(ICON_PF_BUTTON_TRIANGLE, FSUI_VSTR("Change View")), - std::make_pair(ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Launch Options")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_SQUARE : ICON_PF_BUTTON_TRIANGLE, FSUI_VSTR("Change View")), + std::make_pair(swapNorthWest ? ICON_PF_BUTTON_TRIANGLE : ICON_PF_BUTTON_SQUARE, FSUI_VSTR("Launch Options")), std::make_pair(circleOK ? ICON_PF_BUTTON_CIRCLE : ICON_PF_BUTTON_CROSS, FSUI_VSTR("Start Game")), std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, FSUI_VSTR("Back")), }); diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index eaa1c7e93f..68dd88c2da 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -89,6 +89,8 @@ static std::atomic_bool s_imgui_wants_keyboard{false}; static std::atomic_bool s_imgui_wants_mouse{false}; static std::atomic_bool s_imgui_wants_text{false}; +static bool s_gamepad_swap_noth_west = false; + // mapping of host key -> imgui key static std::unordered_map s_imgui_key_map; @@ -959,7 +961,15 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout return false; MTGS::RunOnGSThread( - [key = key_map[static_cast(key)], value, layout]() { + [key = key_map[static_cast(key)], value, layout]() mutable { + if (s_gamepad_swap_noth_west) + { + if (key == ImGuiKey_GamepadFaceUp) + key = ImGuiKey_GamepadFaceLeft; + else if (key == ImGuiKey_GamepadFaceLeft) + key = ImGuiKey_GamepadFaceUp; + } + ImGuiFullscreen::ReportGamepadLayout(layout); ImGui::GetIO().AddKeyAnalogEvent(key, (value > 0.0f), value); }); @@ -967,6 +977,16 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, InputLayout return s_imgui_wants_keyboard.load(std::memory_order_acquire); } +void ImGuiManager::SwapGamepadNorthWest(bool value) +{ + s_gamepad_swap_noth_west = value; +} + +bool ImGuiManager::IsGamepadNorthWestSwapped() +{ + return s_gamepad_swap_noth_west; +} + void ImGuiManager::CreateSoftwareCursorTextures() { for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) diff --git a/pcsx2/ImGui/ImGuiManager.h b/pcsx2/ImGui/ImGuiManager.h index 8b5024e66c..5f657d89b2 100644 --- a/pcsx2/ImGui/ImGuiManager.h +++ b/pcsx2/ImGui/ImGuiManager.h @@ -97,6 +97,12 @@ namespace ImGuiManager /// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation. bool ProcessGenericInputEvent(GenericInputBinding key, InputLayout layout, float value); + /// Called to swap North/West gamepad buttons within ImGui + void SwapGamepadNorthWest(bool value); + + /// Checks if the North/West gamepad buttons are swapped within ImGui + bool IsGamepadNorthWestSwapped(); + /// Sets an image and scale for a software cursor. Software cursors can be used for things like crosshairs. void SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color = 0xFFFFFF); bool HasSoftwareCursor(u32 index);