From e0e69202343ea629f26e4c53e8b9054ddd23755f Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 16 Sep 2026 01:17:16 +1000 Subject: [PATCH] FullscreenUI: Add 'Toggle Pause Menu' hotkey --- src/core/fullscreenui.cpp | 87 ++++++++++++++++++++++++++++++++- src/core/fullscreenui.h | 5 +- src/core/fullscreenui_private.h | 2 + src/core/hotkeys.cpp | 11 ++++- 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/core/fullscreenui.cpp b/src/core/fullscreenui.cpp index 0c4d78bdb..fed218314 100644 --- a/src/core/fullscreenui.cpp +++ b/src/core/fullscreenui.cpp @@ -142,6 +142,7 @@ struct Locals MainWindowType current_main_window = MainWindowType::None; PauseSubMenu current_pause_submenu = PauseSubMenu::None; MainWindowType previous_main_window = MainWindowType::None; + MainWindowType toggled_main_window = MainWindowType::None; bool initialized = false; bool background_loaded = false; bool was_paused_on_quick_menu_open = false; @@ -245,6 +246,7 @@ void FullscreenUI::OnSystemStarting() BeginTransition(LONG_TRANSITION_TIME, []() { ClearSaveStateEntryList(); s_locals.current_main_window = MainWindowType::None; + s_locals.toggled_main_window = MainWindowType::None; QueueResetFocus(FocusResetType::ViewChanged); UpdateRunIdleState(); }); @@ -295,6 +297,7 @@ void FullscreenUI::OnSystemDestroyed() s_locals.was_paused_on_quick_menu_open = false; s_locals.current_pause_submenu = PauseSubMenu::None; + s_locals.toggled_main_window = MainWindowType::None; ReturnToMainWindow(TransitionEffect::Fade, LONG_TRANSITION_TIME); }); } @@ -314,9 +317,17 @@ void FullscreenUI::PauseAndOpenMenuFromCoreThread(void (*callback)()) return; Initialize(); - if (!CanCurrentMainWindowStack() || !SetPendingMainWindowSwitch()) + if (!SetPendingMainWindowSwitch()) return; + // Explicit open actions supersede a menu which was hidden by PauseAndToggleMenuFromCoreThread(). + if (s_locals.toggled_main_window != MainWindowType::None) + { + s_locals.toggled_main_window = MainWindowType::None; + s_locals.previous_main_window = MainWindowType::None; + s_locals.current_pause_submenu = PauseSubMenu::None; + } + s_locals.was_paused_on_quick_menu_open = was_paused; callback(); }); @@ -340,6 +351,76 @@ void FullscreenUI::OpenPauseMenu() }); } +void FullscreenUI::PauseAndToggleMenuFromCoreThread(void (*open_callback)(), void (*restored_callback)(), + float transition_time) +{ + DebugAssert(Host::IsOnCoreThread()); + if (!System::IsValid()) + return; + + const bool was_paused = System::IsPaused(); + VideoThread::RunOnThread([open_callback, restored_callback, transition_time, was_paused]() { + Initialize(); + + // Dialogs cannot be hidden safely, since doing so would leave their callbacks pending while the game is running. + // A pending switch means another action already owns the current transition. + if (AreAnyDialogsOpen() || !SetPendingMainWindowSwitch()) + return; + + // Hide the current window without disturbing the state it owns. + if (s_locals.current_main_window != MainWindowType::None) + { + s_locals.toggled_main_window = s_locals.current_main_window; + BeginTransition(TransitionEffect::ZoomOut, SHORT_TRANSITION_TIME, []() { + s_locals.current_main_window = MainWindowType::None; + s_locals.has_pending_window_switch = false; + UpdateRunIdleState(); + FixStateIfPaused(); + UnpauseForMenuClose(); + }); + return; + } + + // Restore the hidden window, or invoke the requested open action when there is no saved window. Start the + // transition before requesting the pause so an extra frame does not display the pause icon. + s_locals.was_paused_on_quick_menu_open = was_paused; + const bool restoring = (s_locals.toggled_main_window != MainWindowType::None); + BeginTransition(TransitionEffect::ZoomIn, transition_time, [open_callback, restored_callback, restoring]() { + ForceKeyNavEnabled(); + EnqueueSoundEffect(SFX_NAV_ACTIVATE); + + if (restoring) + { + s_locals.current_main_window = std::exchange(s_locals.toggled_main_window, MainWindowType::None); + s_locals.has_pending_window_switch = false; + UpdateRunIdleState(); + FixStateIfPaused(); + + if (restored_callback) + restored_callback(); + } + else + { + open_callback(); + } + }); + + if (!was_paused) + Host::RunOnCoreThread([]() { System::PauseSystem(true); }); + }); +} + +void FullscreenUI::TogglePauseMenu() +{ + PauseAndToggleMenuFromCoreThread( + []() { + UpdateAchievementsPauseScreenInfo(); + s_locals.current_pause_submenu = PauseSubMenu::None; + SwitchToMainWindow(MainWindowType::PauseMenu); + }, + &UpdateAchievementsPauseScreenInfo); +} + void FullscreenUI::OpenCheatsMenu() { PauseAndOpenMenuFromCoreThread([]() { @@ -382,6 +463,7 @@ void FullscreenUI::ClosePauseMenu(TransitionEffect effect /*= TransitionEffect:: if (!VideoThread::HasGPUBackend()) return; + s_locals.toggled_main_window = MainWindowType::None; BeginTransition(effect, transition_time, []() { s_locals.current_pause_submenu = PauseSubMenu::None; SwitchToMainWindow(MainWindowType::None); @@ -398,6 +480,7 @@ void FullscreenUI::ClosePauseMenuImmediately() CancelTransition(); UnpauseForMenuClose(); + s_locals.toggled_main_window = MainWindowType::None; s_locals.current_pause_submenu = PauseSubMenu::None; s_locals.has_pending_window_switch = false; SwitchToMainWindow(MainWindowType::None); @@ -504,6 +587,7 @@ void FullscreenUI::ReturnToMainWindow(TransitionEffect effect, float transition_ { BeginTransition(effect, transition_time, []() { s_locals.previous_main_window = MainWindowType::None; + s_locals.toggled_main_window = MainWindowType::None; s_locals.current_pause_submenu = PauseSubMenu::None; s_locals.has_pending_window_switch = false; @@ -544,6 +628,7 @@ void FullscreenUI::Shutdown() s_locals.current_main_window = MainWindowType::None; s_locals.current_pause_submenu = PauseSubMenu::None; s_locals.previous_main_window = MainWindowType::None; + s_locals.toggled_main_window = MainWindowType::None; s_locals.was_paused_on_quick_menu_open = false; s_locals.has_pending_window_switch = false; diff --git a/src/core/fullscreenui.h b/src/core/fullscreenui.h index 034a05569..e790c8c13 100644 --- a/src/core/fullscreenui.h +++ b/src/core/fullscreenui.h @@ -45,10 +45,11 @@ void UpdateRunIdleState(); #ifndef __ANDROID__ void OpenPauseMenu(); +void TogglePauseMenu(); void OpenCheatsMenu(); void OpenDiscChangeMenu(); -void OpenAchievementsWindow(); -void OpenLeaderboardsWindow(); +void ToggleAchievementsWindow(); +void ToggleLeaderboardsWindow(); class BackgroundProgressCallback final : public ProgressCallback { diff --git a/src/core/fullscreenui_private.h b/src/core/fullscreenui_private.h index f2ebed846..006a4669d 100644 --- a/src/core/fullscreenui_private.h +++ b/src/core/fullscreenui_private.h @@ -64,6 +64,8 @@ bool AreAnyDialogsOpen(); bool AreAnyDialogsInteractable(); void PauseAndOpenMenuFromCoreThread(void (*callback)()); +void PauseAndToggleMenuFromCoreThread(void (*open_callback)(), void (*restored_callback)() = nullptr, + float transition_time = SHORT_TRANSITION_TIME); void ClosePauseMenu(TransitionEffect effect = TransitionEffect::Fade, float transition_time = SHORT_TRANSITION_TIME); void ClosePauseMenuImmediately(); diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index 681600f95..8ddd5236e 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -135,6 +135,13 @@ static constexpr const HotkeyInfo s_hotkey_list[] = { FullscreenUI::OpenPauseMenu(); }}, + {"TogglePauseMenu", TRANSLATE_NOOP("Hotkeys", "Interface"), TRANSLATE_NOOP("Hotkeys", "Toggle Pause Menu"), + [](InputButtonEvent event) { + if (event == InputButtonEvent::Released && System::CanPauseSystem(true)) + FullscreenUI::TogglePauseMenu(); + }, + true}, + {"OpenCheatsMenu", TRANSLATE_NOOP("Hotkeys", "Interface"), TRANSLATE_NOOP("Hotkeys", "Open Cheat Settings"), [](InputButtonEvent event) { if (event == InputButtonEvent::Released && System::CanPauseSystem(true)) @@ -144,13 +151,13 @@ static constexpr const HotkeyInfo s_hotkey_list[] = { {"OpenAchievements", TRANSLATE_NOOP("Hotkeys", "Interface"), TRANSLATE_NOOP("Hotkeys", "Open Achievement List"), [](InputButtonEvent event) { if (event == InputButtonEvent::Released && System::CanPauseSystem(true)) - FullscreenUI::OpenAchievementsWindow(); + FullscreenUI::ToggleAchievementsWindow(); }}, {"OpenLeaderboards", TRANSLATE_NOOP("Hotkeys", "Interface"), TRANSLATE_NOOP("Hotkeys", "Open Leaderboard List"), [](InputButtonEvent event) { if (event == InputButtonEvent::Released && System::CanPauseSystem(true)) - FullscreenUI::OpenLeaderboardsWindow(); + FullscreenUI::ToggleLeaderboardsWindow(); }}, {"Screenshot", TRANSLATE_NOOP("Hotkeys", "Interface"), TRANSLATE_NOOP("Hotkeys", "Save Screenshot"),