FullscreenUI: Add navigation sound effects

This commit is contained in:
Stenzek
2025-12-22 21:03:07 +10:00
parent 0897dfcc94
commit 3fe319df51
9 changed files with 91 additions and 9 deletions

View File

@@ -10,6 +10,7 @@
#include "game_list.h"
#include "gpu_thread.h"
#include "host.h"
#include "sound_effect_manager.h"
#include "system.h"
#include "scmversion/scmversion.h"
@@ -123,6 +124,11 @@ static void DrawResumeStateSelector();
static constexpr std::string_view RESUME_STATE_SELECTOR_DIALOG_NAME = "##resume_state_selector";
static constexpr std::string_view ABOUT_DIALOG_NAME = "##about_duckstation";
const char* SFX_NAV_ACTIVATE = "sounds/nav_activate.wav";
const char* SFX_NAV_BACK = "sounds/nav_back.wav";
const char* SFX_NAV_MOVE = "sounds/nav_move.wav";
const char* SFX_CONTENT_START = "sounds/content_start.wav";
//////////////////////////////////////////////////////////////////////////
// State
//////////////////////////////////////////////////////////////////////////
@@ -184,6 +190,7 @@ void FullscreenUI::Initialize()
UpdateRunIdleState();
}
SoundEffectManager::EnsureInitialized();
INFO_LOG("Fullscreen UI initialized.");
}
@@ -303,6 +310,7 @@ void FullscreenUI::OpenPauseMenu()
PauseForMenuOpen(true);
ForceKeyNavEnabled();
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
UpdateAchievementsRecentUnlockAndAlmostThere();
BeginTransition(SHORT_TRANSITION_TIME, []() {
@@ -324,6 +332,7 @@ void FullscreenUI::OpenCheatsMenu()
PauseForMenuOpen(false);
ForceKeyNavEnabled();
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
BeginTransition(SHORT_TRANSITION_TIME, []() {
if (!SwitchToGameSettings(SettingsPage::Cheats))
@@ -449,6 +458,8 @@ void FullscreenUI::ReturnToMainWindow(float transition_time)
void FullscreenUI::Shutdown(bool clear_state)
{
SoundEffectManager::Shutdown();
if (clear_state)
{
s_locals.current_main_window = MainWindowType::None;
@@ -617,6 +628,8 @@ void FullscreenUI::DoStartPath(std::string path, std::string state, std::optiona
if (GPUThread::HasGPUBackend())
return;
EnqueueSoundEffect(SFX_CONTENT_START);
// Stop running idle to prevent game list from being redrawn until we know if startup succeeded.
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::FullscreenUIActive, false);
@@ -1302,11 +1315,19 @@ void FullscreenUI::DrawLandingWindow()
if (!AreAnyDialogsOpen())
{
if (ImGui::IsKeyPressed(ImGuiKey_GamepadBack, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false))
{
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
OpenFixedPopupDialog(ABOUT_DIALOG_NAME);
}
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadStart, false) || ImGui::IsKeyPressed(ImGuiKey_F3, false))
{
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
DoResume();
}
else if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || ImGui::IsKeyPressed(ImGuiKey_F11, false))
{
DoToggleFullscreen();
}
}
if (IsGamepadInputSource())
@@ -1371,7 +1392,10 @@ void FullscreenUI::DrawStartGameWindow()
if (!AreAnyDialogsOpen())
{
if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || ImGui::IsKeyPressed(ImGuiKey_F1, false))
{
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
OpenSaveStateSelector(std::string(), std::string(), true);
}
}
if (IsGamepadInputSource())

View File

@@ -97,6 +97,12 @@ private:
std::string m_title;
};
// Sound effect names.
extern const char* SFX_NAV_ACTIVATE;
extern const char* SFX_NAV_BACK;
extern const char* SFX_NAV_MOVE;
extern const char* SFX_CONTENT_START;
} // namespace FullscreenUI
// Host UI triggers from Big Picture mode.

View File

@@ -608,6 +608,7 @@ void FullscreenUI::OpenAchievementsWindow()
PauseForMenuOpen(false);
ForceKeyNavEnabled();
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
BeginTransition(SHORT_TRANSITION_TIME, &SwitchToAchievements);
});
@@ -687,12 +688,14 @@ void FullscreenUI::DrawSubsetSelector()
if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, true) ||
ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, true) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, true))
{
EnqueueSoundEffect(SFX_NAV_MOVE);
new_subset_id = (i == 0) ? s_achievements_locals.subset_info_list.back().subset_id :
s_achievements_locals.subset_info_list[i - 1].subset_id;
}
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight, true) ||
ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, true) || ImGui::IsKeyPressed(ImGuiKey_RightArrow, true))
{
EnqueueSoundEffect(SFX_NAV_MOVE);
new_subset_id = ((i + 1) == s_achievements_locals.subset_info_list.size()) ?
s_achievements_locals.subset_info_list.front().subset_id :
s_achievements_locals.subset_info_list[i + 1].subset_id;
@@ -1313,6 +1316,7 @@ void FullscreenUI::OpenLeaderboardsWindow()
PauseForMenuOpen(false);
ForceKeyNavEnabled();
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
BeginTransition(SHORT_TRANSITION_TIME, &SwitchToLeaderboards);
});

View File

@@ -336,6 +336,7 @@ void FullscreenUI::DrawGameListWindow()
{
if (ImGui::IsKeyPressed(ImGuiKey_NavGamepadMenu, false) || ImGui::IsKeyPressed(ImGuiKey_F4, false))
{
EnqueueSoundEffect(SFX_NAV_MOVE);
BeginTransition([]() {
s_game_list_locals.game_list_view =
(s_game_list_locals.game_list_view == GameListView::Grid) ? GameListView::List : GameListView::Grid;
@@ -344,10 +345,12 @@ void FullscreenUI::DrawGameListWindow()
}
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadBack, false) || ImGui::IsKeyPressed(ImGuiKey_F2, false))
{
EnqueueSoundEffect(SFX_NAV_BACK);
BeginTransition(&SwitchToSettings);
}
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadStart, false) || ImGui::IsKeyPressed(ImGuiKey_F3, false))
{
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
DoResume();
}
}

View File

@@ -1817,6 +1817,7 @@ void FullscreenUI::DrawSettingsWindow()
if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, true) ||
ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakSlow, true) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, true))
{
EnqueueSoundEffect(SFX_NAV_MOVE);
BeginTransition([page = pages[(index == 0) ? (count - 1) : (index - 1)]]() {
s_settings_locals.settings_page = page;
QueueResetFocus(FocusResetType::Other);
@@ -1826,6 +1827,7 @@ void FullscreenUI::DrawSettingsWindow()
ImGui::IsKeyPressed(ImGuiKey_NavGamepadTweakFast, true) ||
ImGui::IsKeyPressed(ImGuiKey_RightArrow, true))
{
EnqueueSoundEffect(SFX_NAV_MOVE);
BeginTransition([page = pages[(index + 1) % count]]() {
s_settings_locals.settings_page = page;
QueueResetFocus(FocusResetType::Other);
@@ -2180,6 +2182,10 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_VSTR("Draws a border around the currently-selected item for readability."), "Main",
"FullscreenUIMenuBorders", false);
widgets_settings_changed |= DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_VOLUME_HIGH, "Sound Effects"),
FSUI_VSTR("Plays sound effects when navigating and activating menus."),
"Main", "FullscreenUISoundEffects", true);
// use transition to work around double lock
if (widgets_settings_changed)
BeginTransition(0.0f, &FullscreenUI::UpdateWidgetsSettings);

View File

@@ -544,6 +544,7 @@ TRANSLATE_NOOP("FullscreenUI", "Perspective Correct Colors");
TRANSLATE_NOOP("FullscreenUI", "Perspective Correct Textures");
TRANSLATE_NOOP("FullscreenUI", "Pinky Pals");
TRANSLATE_NOOP("FullscreenUI", "Plays sound effects for events such as achievement unlocks and leaderboard submissions.");
TRANSLATE_NOOP("FullscreenUI", "Plays sound effects when navigating and activating menus.");
TRANSLATE_NOOP("FullscreenUI", "Please enter your user name and password for retroachievements.org below. Your password will not be saved in DuckStation, an access token will be generated and used instead.");
TRANSLATE_NOOP("FullscreenUI", "Port {} Controller Type");
TRANSLATE_NOOP("FullscreenUI", "Post-Processing Settings");

View File

@@ -9,6 +9,7 @@
#include "gpu_thread.h"
#include "host.h"
#include "imgui_overlays.h"
#include "sound_effect_manager.h"
#include "system.h"
#include "util/gpu_device.h"
@@ -344,6 +345,16 @@ struct WidgetsState
s32 enum_choice_button_value = 0;
bool enum_choice_button_set = false;
bool had_hovered_menu_item = false;
bool has_hovered_menu_item = false;
bool rendered_menu_item_border = false;
bool had_focus_reset = false;
bool sound_effects_enabled = false;
bool had_sound_effect = false;
ImAnimatedVec2 menu_button_frame_min_animated;
ImAnimatedVec2 menu_button_frame_max_animated;
ChoiceDialog choice_dialog;
FileSelectorDialog file_selector_dialog;
InputStringDialog input_string_dialog;
@@ -351,12 +362,6 @@ struct WidgetsState
ProgressDialog progress_dialog;
MessageDialog message_dialog;
ImAnimatedVec2 menu_button_frame_min_animated;
ImAnimatedVec2 menu_button_frame_max_animated;
bool had_hovered_menu_item = false;
bool has_hovered_menu_item = false;
bool rendered_menu_item_border = false;
std::vector<Notification> notifications;
std::string toast_title;
@@ -455,6 +460,7 @@ void FullscreenUI::UpdateWidgetsSettings()
UIStyle.Animations = Core::GetBaseBoolSettingValue("Main", "FullscreenUIAnimations", true);
UIStyle.SmoothScrolling = Core::GetBaseBoolSettingValue("Main", "FullscreenUISmoothScrolling", true);
UIStyle.MenuBorders = Core::GetBaseBoolSettingValue("Main", "FullscreenUIMenuBorders", false);
s_state.sound_effects_enabled = Core::GetBaseBoolSettingValue("Main", "FullscreenUISoundEffects", true);
s_state.fullscreen_footer_icon_mapping = Core::GetBaseBoolSettingValue("Main", "FullscreenUIDisplayPSIcons", false) ?
s_ps_button_mapping :
@@ -1027,6 +1033,27 @@ void FullscreenUI::EndLayout()
s_state.rendered_menu_item_border = false;
s_state.had_hovered_menu_item = std::exchange(s_state.has_hovered_menu_item, false);
if (!s_state.had_sound_effect)
{
if (GImGui->NavActivateId != 0)
EnqueueSoundEffect(SFX_NAV_ACTIVATE);
else if (GImGui->NavJustMovedToId != 0)
EnqueueSoundEffect(SFX_NAV_MOVE);
}
// Avoid playing the move sound on focus reset, since it'll also be an active previously.
s_state.had_sound_effect = s_state.had_focus_reset;
s_state.had_focus_reset = false;
}
void FullscreenUI::EnqueueSoundEffect(std::string_view sound_effect)
{
if (s_state.had_sound_effect || !s_state.sound_effects_enabled)
return;
SoundEffectManager::EnqueueSoundEffect(sound_effect);
s_state.had_sound_effect = true;
}
FullscreenUI::FixedPopupDialog::FixedPopupDialog() = default;
@@ -1196,6 +1223,9 @@ bool FullscreenUI::ResetFocusHere()
else
ImGui::SetNavWindow(window);
// prevent any sound from playing on the nav change
s_state.had_focus_reset = true;
s_state.focus_reset_queued = FocusResetType::None;
ResetMenuButtonFrame();
@@ -1248,10 +1278,13 @@ bool FullscreenUI::WantsToCloseMenu()
s_state.close_button_state = CloseButtonState::GamepadPressed;
}
else if ((s_state.close_button_state == CloseButtonState::KeyboardPressed && ImGui::IsKeyReleased(ImGuiKey_Escape)) ||
(s_state.close_button_state == CloseButtonState::MousePressed &&
ImGui::IsKeyReleased(ImGuiKey_MouseRight)) ||
(s_state.close_button_state == CloseButtonState::GamepadPressed &&
ImGui::IsKeyReleased(ImGuiKey_NavGamepadCancel)))
{
EnqueueSoundEffect(SFX_NAV_BACK);
s_state.close_button_state = CloseButtonState::AnyReleased;
}
else if ((s_state.close_button_state == CloseButtonState::MousePressed && ImGui::IsKeyReleased(ImGuiKey_MouseRight)))
{
s_state.close_button_state = CloseButtonState::AnyReleased;
}

View File

@@ -268,6 +268,9 @@ void UpdateTransitionState();
void BeginLayout();
void EndLayout();
/// Enqueues a sound effect, and prevents any other sound effects from being started this frame.
void EnqueueSoundEffect(std::string_view sound_effect);
bool IsAnyFixedPopupDialogOpen();
bool IsFixedPopupDialogOpen(std::string_view name);
void OpenFixedPopupDialog(std::string_view name);

View File

@@ -2064,7 +2064,9 @@ void System::DestroySystem()
FreeMemoryStateStorage(true, true, false);
SoundEffectManager::Shutdown();
// unless fsui is running, we don't need sound effects anymore
if (!GPUThread::IsFullscreenUIRequested())
SoundEffectManager::Shutdown();
GPUThread::DestroyGPUBackend();