From 501380ec7ce89db3e5b3aeaecd1aec88a44b59fc Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 3 Aug 2025 15:02:46 +1000 Subject: [PATCH] Achievements: Split up overlay setting Split into challenge indicator modes, leaderboard trackers, and progress indicators. --- dep/msvc/vsprops/QtCompile.props | 2 +- src/core/achievements.cpp | 91 ++++++++++++++++--- src/core/fullscreen_ui.cpp | 54 +++++++---- src/core/settings.cpp | 79 +++++++++++++--- src/core/settings.h | 25 +++-- src/core/types.h | 10 ++ .../achievementsettingswidget.cpp | 58 +++++++----- .../achievementsettingswidget.ui | 44 +++++++-- 8 files changed, 278 insertions(+), 85 deletions(-) diff --git a/dep/msvc/vsprops/QtCompile.props b/dep/msvc/vsprops/QtCompile.props index 93139b21e..4be5514c4 100644 --- a/dep/msvc/vsprops/QtCompile.props +++ b/dep/msvc/vsprops/QtCompile.props @@ -80,7 +80,7 @@ -DQT_NO_DEBUG -DNDEBUG $(MocDefines) - -I"$(QtIncludeDir)." -I"$(SolutionDir)pcsx2" "-I$(SolutionDir)." -I. + -I"$(QtIncludeDir)." -I. leaderboard_tracker->id, event->leaderboard_tracker->display); + if (!g_settings.achievements_leaderboard_trackers) + return; + const u32 id = event->leaderboard_tracker->id; auto it = std::find_if(s_state.active_leaderboard_trackers.begin(), s_state.active_leaderboard_trackers.end(), [id](const auto& it) { return it.tracker_id == id; }); @@ -1636,25 +1646,26 @@ void Achievements::HandleLeaderboardTrackerShowEvent(const rc_client_event_t* ev void Achievements::HandleLeaderboardTrackerHideEvent(const rc_client_event_t* event) { const u32 id = event->leaderboard_tracker->id; + DEV_LOG("Hiding leaderboard tracker: {}", id); + auto it = std::find_if(s_state.active_leaderboard_trackers.begin(), s_state.active_leaderboard_trackers.end(), [id](const auto& it) { return it.tracker_id == id; }); if (it == s_state.active_leaderboard_trackers.end()) return; - DEV_LOG("Hiding leaderboard tracker: {}", id); it->active = false; } void Achievements::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* event) { const u32 id = event->leaderboard_tracker->id; + DEV_LOG("Updating leaderboard tracker: {}: {}", id, event->leaderboard_tracker->display); + auto it = std::find_if(s_state.active_leaderboard_trackers.begin(), s_state.active_leaderboard_trackers.end(), [id](const auto& it) { return it.tracker_id == id; }); if (it == s_state.active_leaderboard_trackers.end()) return; - DEV_LOG("Updating leaderboard tracker: {}: {}", event->leaderboard_tracker->id, event->leaderboard_tracker->display); - it->text = event->leaderboard_tracker->display; it->active = true; } @@ -1670,9 +1681,27 @@ void Achievements::HandleAchievementChallengeIndicatorShowEvent(const rc_client_ return; } + std::string badge_path = GetAchievementBadgePath(event->achievement, false); + + // we still track these even if the option is disabled, so that they can be displayed in the pause menu + if (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Notification) + { + std::string description = fmt::format(TRANSLATE_FS("Achievements", "Challenge Started: {}"), + event->achievement->description ? event->achievement->description : ""); + GPUThread::RunOnThread([title = std::string(event->achievement->title), description = std::move(description), + badge_path, id = event->achievement->id]() mutable { + if (!FullscreenUI::Initialize()) + return; + + ImGuiFullscreen::AddNotification(fmt::format("AchievementChallenge{}", id), LEADERBOARD_STARTED_NOTIFICATION_TIME, + std::move(title), std::move(description), std::move(badge_path)); + }); + } + s_state.active_challenge_indicators.push_back( AchievementChallengeIndicator{.achievement = event->achievement, - .badge_path = GetAchievementBadgePath(event->achievement, false), + .badge_path = std::move(badge_path), + .time_remaining = LEADERBOARD_STARTED_NOTIFICATION_TIME, .opacity = 0.0f, .active = true}); @@ -1688,6 +1717,15 @@ void Achievements::HandleAchievementChallengeIndicatorHideEvent(const rc_client_ return; DEV_LOG("Hide challenge indicator for {} ({})", event->achievement->id, event->achievement->title); + + if (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Notification || + g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Disabled) + { + // remove it here, because it won't naturally decay + s_state.active_challenge_indicators.erase(it); + return; + } + it->active = false; } @@ -1696,6 +1734,9 @@ void Achievements::HandleAchievementProgressIndicatorShowEvent(const rc_client_e DEV_LOG("Showing progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title, event->achievement->measured_progress); + if (!g_settings.achievements_progress_indicators) + return; + if (!s_state.active_progress_indicator.has_value()) s_state.active_progress_indicator.emplace(); @@ -1711,6 +1752,13 @@ void Achievements::HandleAchievementProgressIndicatorHideEvent(const rc_client_e return; DEV_LOG("Hiding progress indicator"); + + if (!g_settings.achievements_progress_indicators) + { + s_state.active_progress_indicator.reset(); + return; + } + s_state.active_progress_indicator->active = false; } @@ -1718,6 +1766,9 @@ void Achievements::HandleAchievementProgressIndicatorUpdateEvent(const rc_client { DEV_LOG("Updating progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title, event->achievement->measured_progress); + if (!s_state.active_progress_indicator.has_value()) + return; + s_state.active_progress_indicator->achievement = event->achievement; s_state.active_progress_indicator->active = true; } @@ -2286,11 +2337,10 @@ void Achievements::ClearUIState() s_state.achievement_nearest_completion.reset(); } -template -static float IndicatorOpacity(float delta_time, T& i) +static float IndicatorOpacity(float delta_time, bool active, float& opacity) { float target, rate; - if (i.active) + if (active) { target = 1.0f; rate = Achievements::INDICATOR_FADE_IN_TIME; @@ -2301,10 +2351,10 @@ static float IndicatorOpacity(float delta_time, T& i) rate = -Achievements::INDICATOR_FADE_OUT_TIME; } - if (i.opacity != target) - i.opacity = ImSaturate(i.opacity + (delta_time / rate)); + if (opacity != target) + opacity = ImSaturate(opacity + (delta_time / rate)); - return i.opacity; + return opacity; } void Achievements::DrawGameOverlays() @@ -2314,7 +2364,7 @@ void Achievements::DrawGameOverlays() using ImGuiFullscreen::RenderShadowedTextClipped; using ImGuiFullscreen::UIStyle; - if (!HasActiveGame() || !g_settings.achievements_overlays) + if (!HasActiveGame()) return; const auto lock = GetLock(); @@ -2331,15 +2381,26 @@ void Achievements::DrawGameOverlays() ImVec2 position = ImVec2(io.DisplaySize.x - margin, io.DisplaySize.y - margin); ImDrawList* dl = ImGui::GetBackgroundDrawList(); - if (!s_state.active_challenge_indicators.empty()) + if (!s_state.active_challenge_indicators.empty() && + (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::PersistentIcon || + g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::TemporaryIcon)) { + const bool use_time_remaining = + (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::TemporaryIcon); const float x_advance = image_size.x + spacing; ImVec2 current_position = ImVec2(position.x - image_size.x, position.y - image_size.y); for (auto it = s_state.active_challenge_indicators.begin(); it != s_state.active_challenge_indicators.end();) { AchievementChallengeIndicator& indicator = *it; - const float opacity = IndicatorOpacity(io.DeltaTime, indicator); + bool active = indicator.active; + if (use_time_remaining) + { + indicator.time_remaining = std::max(indicator.time_remaining - io.DeltaTime, 0.0f); + active = (indicator.time_remaining > 0.0f); + } + + const float opacity = IndicatorOpacity(io.DeltaTime, active, indicator.opacity); GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(indicator.badge_path); if (badge) @@ -2366,7 +2427,7 @@ void Achievements::DrawGameOverlays() if (s_state.active_progress_indicator.has_value()) { AchievementProgressIndicator& indicator = s_state.active_progress_indicator.value(); - const float opacity = IndicatorOpacity(io.DeltaTime, indicator); + const float opacity = IndicatorOpacity(io.DeltaTime, indicator.active, indicator.opacity); const std::string_view text = s_state.active_progress_indicator->achievement->measured_progress; const ImVec2 text_size = UIStyle.Font->CalcTextSizeA(UIStyle.MediumFontSize, UIStyle.NormalFontWeight, FLT_MAX, @@ -2408,7 +2469,7 @@ void Achievements::DrawGameOverlays() for (auto it = s_state.active_leaderboard_trackers.begin(); it != s_state.active_leaderboard_trackers.end();) { LeaderboardTrackerIndicator& indicator = *it; - const float opacity = IndicatorOpacity(io.DeltaTime, indicator); + const float opacity = IndicatorOpacity(io.DeltaTime, indicator.active, indicator.opacity); TinyString width_string; width_string.append(ICON_FA_STOPWATCH); diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 8c509dd26..17011338a 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -6414,21 +6414,6 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock& se } } - DrawToggleSetting( - bsi, FSUI_ICONVSTR(ICON_FA_BELL, "Achievement Notifications"), - FSUI_VSTR("Displays popup messages on events such as achievement unlocks and leaderboard submissions."), "Cheevos", - "Notifications", true, enabled); - DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIST_OL, "Leaderboard Notifications"), - FSUI_VSTR("Displays popup messages when starting, submitting, or failing a leaderboard challenge."), - "Cheevos", "LeaderboardNotifications", true, enabled); - DrawToggleSetting( - bsi, FSUI_ICONVSTR(ICON_FA_MUSIC, "Sound Effects"), - FSUI_VSTR("Plays sound effects for events such as achievement unlocks and leaderboard submissions."), "Cheevos", - "SoundEffects", true, enabled); - DrawToggleSetting( - bsi, FSUI_ICONVSTR(ICON_FA_WAND_MAGIC_SPARKLES, "Enable In-Game Overlays"), - FSUI_VSTR("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."), - "Cheevos", "Overlays", true, enabled); DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_ARROW_ROTATE_RIGHT, "Encore Mode"), FSUI_VSTR("When enabled, each session will behave as if no achievements have been unlocked."), "Cheevos", "EncoreMode", false, enabled); @@ -6442,6 +6427,34 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock& se "tracked by RetroAchievements."), "Cheevos", "UnofficialTestMode", false, enabled); + DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_BELL, "Achievement Notifications"), + FSUI_VSTR("Displays popup messages on events such as achievement unlocks and leaderboard submissions."), "Cheevos", + "Notifications", true, enabled); + DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_LIST_OL, "Leaderboard Notifications"), + FSUI_VSTR("Displays popup messages when starting, submitting, or failing a leaderboard challenge."), + "Cheevos", "LeaderboardNotifications", true, enabled); + DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_CLOCK, "Leaderboard Trackers"), + FSUI_VSTR("Shows a timer in the bottom-right corner of the screen when leaderboard challenges are active."), + "Cheevos", "LeaderboardTrackers", true, enabled); + DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_MUSIC, "Sound Effects"), + FSUI_VSTR("Plays sound effects for events such as achievement unlocks and leaderboard submissions."), "Cheevos", + "SoundEffects", true, enabled); + DrawToggleSetting( + bsi, FSUI_ICONVSTR(ICON_FA_BARS_PROGRESS, "Progress Indicators"), + FSUI_VSTR( + "Shows a popup in the lower-right corner of the screen when progress towards a measured achievement changes."), + "Cheevos", "ProgressIndicators", true, enabled); + DrawEnumSetting( + bsi, FSUI_ICONVSTR(ICON_FA_TEMPERATURE_ARROW_UP, "Challenge Indicators"), + FSUI_VSTR("Shows a notification or icons in the lower-right corner of the screen when a challenge/primed " + "achievement is active."), + "Cheevos", "ChallengeIndicatorMode", Settings::DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE, + &Settings::ParseAchievementChallengeIndicatorMode, &Settings::GetAchievementChallengeIndicatorModeName, + &Settings::GetAchievementChallengeIndicatorModeDisplayName, AchievementChallengeIndicatorMode::MaxCount, enabled); + if (!IsEditingGameSettings(bsi)) { if (MenuButton(FSUI_ICONVSTR(ICON_FA_ARROWS_ROTATE, "Update Progress"), @@ -6461,7 +6474,8 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock& se Host::NumberFormatType::LongDateTime, StringUtil::FromChars(bsi->GetTinyStringValue("Cheevos", "LoginTimestamp", "0")).value_or(0)); MenuButtonWithoutSummary( - SmallString::from_format(fmt::runtime(FSUI_ICONVSTR(ICON_FA_CLOCK, "Login token generated on {}")), ts_string), + SmallString::from_format(fmt::runtime(FSUI_ICONVSTR(ICON_FA_USER_CLOCK, "Login token generated on {}")), + ts_string), false); } } @@ -9478,6 +9492,7 @@ TRANSLATE_NOOP("FullscreenUI", "CPU Emulation"); TRANSLATE_NOOP("FullscreenUI", "CPU Mode"); TRANSLATE_NOOP("FullscreenUI", "Cancel"); TRANSLATE_NOOP("FullscreenUI", "Capture"); +TRANSLATE_NOOP("FullscreenUI", "Challenge Indicators"); TRANSLATE_NOOP("FullscreenUI", "Change Disc"); TRANSLATE_NOOP("FullscreenUI", "Change Page"); TRANSLATE_NOOP("FullscreenUI", "Change Selection"); @@ -9608,7 +9623,6 @@ TRANSLATE_NOOP("FullscreenUI", "Enable Discord Presence"); TRANSLATE_NOOP("FullscreenUI", "Enable Fast Boot"); TRANSLATE_NOOP("FullscreenUI", "Enable GPU-Based Validation"); TRANSLATE_NOOP("FullscreenUI", "Enable GPU-based validation when supported by the host's renderer API. Only for developer use."); -TRANSLATE_NOOP("FullscreenUI", "Enable In-Game Overlays"); TRANSLATE_NOOP("FullscreenUI", "Enable Overclocking"); TRANSLATE_NOOP("FullscreenUI", "Enable Post Processing"); TRANSLATE_NOOP("FullscreenUI", "Enable Recompiler Block Linking"); @@ -9739,6 +9753,7 @@ TRANSLATE_NOOP("FullscreenUI", "Launch a game by selecting a file/disc image."); TRANSLATE_NOOP("FullscreenUI", "Launch a game from a file, disc, or starts the console without any disc inserted."); TRANSLATE_NOOP("FullscreenUI", "Launch a game from images scanned from your game directories."); TRANSLATE_NOOP("FullscreenUI", "Leaderboard Notifications"); +TRANSLATE_NOOP("FullscreenUI", "Leaderboard Trackers"); TRANSLATE_NOOP("FullscreenUI", "Leaderboards"); TRANSLATE_NOOP("FullscreenUI", "Left: "); TRANSLATE_NOOP("FullscreenUI", "Light"); @@ -9855,6 +9870,7 @@ TRANSLATE_NOOP("FullscreenUI", "Press To Toggle"); TRANSLATE_NOOP("FullscreenUI", "Pressure"); TRANSLATE_NOOP("FullscreenUI", "Prevents the emulator from producing any audible sound."); TRANSLATE_NOOP("FullscreenUI", "Prevents the screen saver from activating and the host from sleeping while emulation is running."); +TRANSLATE_NOOP("FullscreenUI", "Progress Indicators"); TRANSLATE_NOOP("FullscreenUI", "Provides vibration and LED control support over Bluetooth."); TRANSLATE_NOOP("FullscreenUI", "Purple Rain"); TRANSLATE_NOOP("FullscreenUI", "Push a controller button or axis now."); @@ -9989,9 +10005,11 @@ TRANSLATE_NOOP("FullscreenUI", "Show Resolution"); TRANSLATE_NOOP("FullscreenUI", "Show Speed"); TRANSLATE_NOOP("FullscreenUI", "Show Status Indicators"); TRANSLATE_NOOP("FullscreenUI", "Shows a background image or shader when a game isn't running. Backgrounds are located in resources/fullscreenui/backgrounds in the data directory."); +TRANSLATE_NOOP("FullscreenUI", "Shows a notification or icons in the lower-right corner of the screen when a challenge/primed achievement is active."); +TRANSLATE_NOOP("FullscreenUI", "Shows a popup in the lower-right corner of the screen when progress towards a measured achievement changes."); +TRANSLATE_NOOP("FullscreenUI", "Shows a timer in the bottom-right corner of the screen when leaderboard challenges are active."); TRANSLATE_NOOP("FullscreenUI", "Shows a visual history of frame times in the upper-left corner of the display."); TRANSLATE_NOOP("FullscreenUI", "Shows enhancement settings in the bottom-right corner of the screen."); -TRANSLATE_NOOP("FullscreenUI", "Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."); TRANSLATE_NOOP("FullscreenUI", "Shows information about input and audio latency in the top-right corner of the display."); TRANSLATE_NOOP("FullscreenUI", "Shows information about the emulated GPU in the top-right corner of the display."); TRANSLATE_NOOP("FullscreenUI", "Shows on-screen-display messages when events occur."); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index fd653563e..21ed12ef8 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -437,18 +437,27 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro achievements_enabled = si.GetBoolValue("Cheevos", "Enabled", false); achievements_hardcore_mode = si.GetBoolValue("Cheevos", "ChallengeMode", false); - achievements_notifications = si.GetBoolValue("Cheevos", "Notifications", true); - achievements_leaderboard_notifications = si.GetBoolValue("Cheevos", "LeaderboardNotifications", true); - achievements_sound_effects = si.GetBoolValue("Cheevos", "SoundEffects", true); - achievements_overlays = si.GetBoolValue("Cheevos", "Overlays", true); achievements_encore_mode = si.GetBoolValue("Cheevos", "EncoreMode", false); achievements_spectator_mode = si.GetBoolValue("Cheevos", "SpectatorMode", false); achievements_unofficial_test_mode = si.GetBoolValue("Cheevos", "UnofficialTestMode", false); achievements_use_raintegration = si.GetBoolValue("Cheevos", "UseRAIntegration", false); + achievements_notifications = si.GetBoolValue("Cheevos", "Notifications", true); + achievements_leaderboard_notifications = si.GetBoolValue("Cheevos", "LeaderboardNotifications", true); + achievements_leaderboard_trackers = si.GetBoolValue("Cheevos", "LeaderboardTrackers", true); + achievements_sound_effects = si.GetBoolValue("Cheevos", "SoundEffects", true); + achievements_progress_indicators = si.GetBoolValue("Cheevos", "ProgressIndicators", true); + achievements_challenge_indicator_mode = + ParseAchievementChallengeIndicatorMode( + si.GetStringValue("Cheevos", "ChallengeIndicatorMode", + GetAchievementChallengeIndicatorModeName(DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE)) + .c_str()) + .value_or(DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE); achievements_notification_duration = - si.GetIntValue("Cheevos", "NotificationsDuration", DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME); + Truncate8(std::min(si.GetUIntValue("Cheevos", "NotificationsDuration", DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME), + std::numeric_limits::max())); achievements_leaderboard_duration = - si.GetIntValue("Cheevos", "LeaderboardsDuration", DEFAULT_LEADERBOARD_NOTIFICATION_TIME); + Truncate8(std::min(si.GetUIntValue("Cheevos", "LeaderboardsDuration", DEFAULT_LEADERBOARD_NOTIFICATION_TIME), + std::numeric_limits::max())); #ifndef __ANDROID__ enable_gdb_server = si.GetBoolValue("Debug", "EnableGDBServer"); @@ -737,16 +746,19 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const si.SetBoolValue("Cheevos", "Enabled", achievements_enabled); si.SetBoolValue("Cheevos", "ChallengeMode", achievements_hardcore_mode); - si.SetBoolValue("Cheevos", "Notifications", achievements_notifications); - si.SetBoolValue("Cheevos", "LeaderboardNotifications", achievements_leaderboard_notifications); - si.SetBoolValue("Cheevos", "SoundEffects", achievements_sound_effects); - si.SetBoolValue("Cheevos", "Overlays", achievements_overlays); si.SetBoolValue("Cheevos", "EncoreMode", achievements_encore_mode); si.SetBoolValue("Cheevos", "SpectatorMode", achievements_spectator_mode); si.SetBoolValue("Cheevos", "UnofficialTestMode", achievements_unofficial_test_mode); si.SetBoolValue("Cheevos", "UseRAIntegration", achievements_use_raintegration); - si.SetIntValue("Cheevos", "NotificationsDuration", achievements_notification_duration); - si.SetIntValue("Cheevos", "LeaderboardsDuration", achievements_leaderboard_duration); + si.SetBoolValue("Cheevos", "Notifications", achievements_notifications); + si.SetBoolValue("Cheevos", "LeaderboardNotifications", achievements_leaderboard_notifications); + si.SetBoolValue("Cheevos", "LeaderboardTrackers", achievements_leaderboard_trackers); + si.SetBoolValue("Cheevos", "SoundEffects", achievements_sound_effects); + si.SetBoolValue("Cheevos", "ProgressIndicators", achievements_progress_indicators); + si.SetStringValue("Cheevos", "ChallengeIndicatorMode", + GetAchievementChallengeIndicatorModeName(achievements_challenge_indicator_mode)); + si.SetUIntValue("Cheevos", "NotificationsDuration", achievements_notification_duration); + si.SetUIntValue("Cheevos", "LeaderboardsDuration", achievements_leaderboard_duration); #ifndef __ANDROID__ si.SetBoolValue("Debug", "EnableGDBServer", enable_gdb_server); @@ -2156,6 +2168,49 @@ std::optional Settings::GetDisplayScreenshotFormatFromF return std::nullopt; } +static constexpr const std::array s_achievement_challenge_indicator_mode_names = { + "Disabled", + "PersistentIcon", + "TemporaryIcon", + "Notification", +}; +static_assert(s_achievement_challenge_indicator_mode_names.size() == + static_cast(AchievementChallengeIndicatorMode::MaxCount)); +static constexpr const std::array s_achievement_challenge_indicator_mode_display_names = { + TRANSLATE_DISAMBIG_NOOP("Settings", "Disabled", "AchievementChallengeIndicatorMode"), + TRANSLATE_DISAMBIG_NOOP("Settings", "Show Persistent Icons", "AchievementChallengeIndicatorMode"), + TRANSLATE_DISAMBIG_NOOP("Settings", "Show Temporary Icons", "AchievementChallengeIndicatorMode"), + TRANSLATE_DISAMBIG_NOOP("Settings", "Show Notifications", "AchievementChallengeIndicatorMode"), +}; +static_assert(s_achievement_challenge_indicator_mode_display_names.size() == + static_cast(AchievementChallengeIndicatorMode::MaxCount)); + +std::optional Settings::ParseAchievementChallengeIndicatorMode(const char* str) +{ + int index = 0; + for (const char* name : s_achievement_challenge_indicator_mode_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetAchievementChallengeIndicatorModeName(AchievementChallengeIndicatorMode mode) +{ + return s_achievement_challenge_indicator_mode_names[static_cast(mode)]; +} + +const char* Settings::GetAchievementChallengeIndicatorModeDisplayName(AchievementChallengeIndicatorMode mode) +{ + return Host::TranslateToCString("Settings", + s_achievement_challenge_indicator_mode_display_names[static_cast(mode)], + "AchievementChallengeIndicatorMode"); +} + static constexpr const std::array s_memory_card_type_names = { "None", "Shared", "PerGame", "PerGameTitle", "PerGameFileTitle", "NonPersistent", }; diff --git a/src/core/settings.h b/src/core/settings.h index 26cb8b0ff..762920616 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -347,16 +347,19 @@ struct Settings : public GPUSettings // achievements bool achievements_enabled : 1 = false; bool achievements_hardcore_mode : 1 = false; - bool achievements_notifications : 1 = true; - bool achievements_leaderboard_notifications : 1 = true; - bool achievements_sound_effects : 1 = true; - bool achievements_overlays : 1 = true; bool achievements_encore_mode : 1 = false; bool achievements_spectator_mode : 1 = false; bool achievements_unofficial_test_mode : 1 = false; bool achievements_use_raintegration : 1 = false; - s32 achievements_notification_duration = DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME; - s32 achievements_leaderboard_duration = DEFAULT_LEADERBOARD_NOTIFICATION_TIME; + bool achievements_notifications : 1 = true; + bool achievements_leaderboard_notifications : 1 = true; + bool achievements_leaderboard_trackers : 1 = true; + bool achievements_sound_effects : 1 = true; + bool achievements_progress_indicators : 1 = true; + AchievementChallengeIndicatorMode achievements_challenge_indicator_mode = + DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE; + u8 achievements_notification_duration = DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME; + u8 achievements_leaderboard_duration = DEFAULT_LEADERBOARD_NOTIFICATION_TIME; float emulation_speed = 1.0f; float fast_forward_speed = 0.0f; @@ -546,6 +549,10 @@ struct Settings : public GPUSettings static const char* GetDisplayScreenshotModeName(DisplayScreenshotMode mode); static const char* GetDisplayScreenshotModeDisplayName(DisplayScreenshotMode mode); + static std::optional ParseAchievementChallengeIndicatorMode(const char* str); + static const char* GetAchievementChallengeIndicatorModeName(AchievementChallengeIndicatorMode mode); + static const char* GetAchievementChallengeIndicatorModeDisplayName(AchievementChallengeIndicatorMode mode); + static std::optional ParseDisplayScreenshotFormat(const char* str); static const char* GetDisplayScreenshotFormatName(DisplayScreenshotFormat mode); static const char* GetDisplayScreenshotFormatDisplayName(DisplayScreenshotFormat mode); @@ -602,8 +609,10 @@ struct Settings : public GPUSettings static constexpr MultitapMode DEFAULT_MULTITAP_MODE = MultitapMode::Disabled; static constexpr PIODeviceType DEFAULT_PIO_DEVICE_TYPE = PIODeviceType::None; - static constexpr s32 DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME = 5; - static constexpr s32 DEFAULT_LEADERBOARD_NOTIFICATION_TIME = 10; + static constexpr AchievementChallengeIndicatorMode DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE = + AchievementChallengeIndicatorMode::PersistentIcon; + static constexpr u8 DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME = 5; + static constexpr u8 DEFAULT_LEADERBOARD_NOTIFICATION_TIME = 10; static constexpr Log::Level DEFAULT_LOG_LEVEL = Log::Level::Info; diff --git a/src/core/types.h b/src/core/types.h index d0d614063..0608de4e8 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -228,6 +228,16 @@ enum class DisplayScreenshotFormat : u8 Count }; +enum class AchievementChallengeIndicatorMode : u8 +{ + Disabled, + PersistentIcon, + TemporaryIcon, + Notification, + + MaxCount +}; + enum class ControllerType : u8 { None, diff --git a/src/duckstation-qt/achievementsettingswidget.cpp b/src/duckstation-qt/achievementsettingswidget.cpp index 957132e67..6c845ec8d 100644 --- a/src/duckstation-qt/achievementsettingswidget.cpp +++ b/src/duckstation-qt/achievementsettingswidget.cpp @@ -27,38 +27,33 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Cheevos", "Enabled", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hardcoreMode, "Cheevos", "ChallengeMode", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Cheevos", "Notifications", true); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Cheevos", - "LeaderboardNotifications", true); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Cheevos", "SoundEffects", true); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.overlays, "Cheevos", "Overlays", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Cheevos", "EncoreMode", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Cheevos", "SpectatorMode", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialAchievements, "Cheevos", "UnofficialTestMode", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Cheevos", "Notifications", true); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.achievementNotificationsDuration, "Cheevos", "NotificationsDuration", Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Cheevos", + "LeaderboardNotifications", true); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.leaderboardNotificationsDuration, "Cheevos", "LeaderboardsDuration", Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardTrackers, "Cheevos", "LeaderboardTrackers", true); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Cheevos", "SoundEffects", true); + SettingWidgetBinder::BindWidgetToEnumSetting( + sif, m_ui.challengeIndicatorMode, "Cheevos", "ChallengeIndicatorMode", + &Settings::ParseAchievementChallengeIndicatorMode, &Settings::GetAchievementChallengeIndicatorModeName, + &Settings::GetAchievementChallengeIndicatorModeDisplayName, Settings::DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE, + AchievementChallengeIndicatorMode::MaxCount); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.progressIndicators, "Cheevos", "ProgressIndicators", true); dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, DuckStation will scan for achievements on startup.")); dialog->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"), tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save " "state, cheats, and slowdown functions.")); - dialog->registerWidgetHelp(m_ui.achievementNotifications, tr("Show Achievement Notifications"), tr("Checked"), - tr("Displays popup messages on events such as achievement unlocks and game completion.")); - dialog->registerWidgetHelp( - m_ui.leaderboardNotifications, tr("Show Leaderboard Notifications"), tr("Checked"), - tr("Displays popup messages when starting, submitting, or failing a leaderboard challenge.")); - dialog->registerWidgetHelp( - m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"), - tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions.")); - dialog->registerWidgetHelp( - m_ui.overlays, tr("Enable In-Game Overlays"), tr("Checked"), - tr("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active.")); dialog->registerWidgetHelp(m_ui.encoreMode, tr("Enable Encore Mode"), tr("Unchecked"), tr("When enabled, each session will behave as if no achievements have been unlocked.")); dialog->registerWidgetHelp(m_ui.spectatorMode, tr("Enable Spectator Mode"), tr("Unchecked"), @@ -68,6 +63,23 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi m_ui.unofficialAchievements, tr("Test Unofficial Achievements"), tr("Unchecked"), tr("When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are " "not tracked by RetroAchievements, so they unlock every time.")); + dialog->registerWidgetHelp(m_ui.achievementNotifications, tr("Show Achievement Notifications"), tr("Checked"), + tr("Displays popup messages on events such as achievement unlocks and game completion.")); + dialog->registerWidgetHelp( + m_ui.leaderboardNotifications, tr("Show Leaderboard Notifications"), tr("Checked"), + tr("Displays popup messages when starting, submitting, or failing a leaderboard challenge.")); + dialog->registerWidgetHelp( + m_ui.leaderboardTrackers, tr("Show Leaderboard Trackers"), tr("Checked"), + tr("Shows a timer in the bottom-right corner of the screen when leaderboard challenges are active.")); + dialog->registerWidgetHelp( + m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"), + tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions.")); + dialog->registerWidgetHelp(m_ui.challengeIndicatorMode, tr("Challenge Indicators"), tr("Show Persistent Icons"), + tr("Shows a notification or icons in the lower-right corner of the screen when a " + "challenge/primed achievement is active.")); + dialog->registerWidgetHelp( + m_ui.progressIndicators, tr("Show Progress Indicators"), tr("Checked"), + tr("Shows a popup in the lower-right corner of the screen when progress towards a measured achievement changes.")); connect(m_ui.enable, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.hardcoreMode, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); @@ -140,8 +152,11 @@ void AchievementSettingsWidget::updateEnableState() m_ui.achievementNotificationsDurationLabel->setEnabled(notifications); m_ui.leaderboardNotificationsDuration->setEnabled(lb_notifications); m_ui.leaderboardNotificationsDurationLabel->setEnabled(lb_notifications); + m_ui.leaderboardTrackers->setEnabled(enabled); m_ui.soundEffects->setEnabled(enabled); - m_ui.overlays->setEnabled(enabled); + m_ui.challengeIndicatorMode->setEnabled(enabled); + m_ui.challengeIndicatorModeLabel->setEnabled(enabled); + m_ui.progressIndicators->setEnabled(enabled); m_ui.encoreMode->setEnabled(enabled); m_ui.spectatorMode->setEnabled(enabled); m_ui.unofficialAchievements->setEnabled(enabled); @@ -199,11 +214,10 @@ void AchievementSettingsWidget::updateLoginState() { const u64 login_unix_timestamp = StringUtil::FromChars(Host::GetBaseStringSettingValue("Cheevos", "LoginTimestamp", "0")).value_or(0); - const QString login_timestamp = QtHost::FormatNumber(Host::NumberFormatType::ShortDateTime, - static_cast(login_unix_timestamp)); - m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.") - .arg(QString::fromStdString(username)) - .arg(login_timestamp)); + const QString login_timestamp = + QtHost::FormatNumber(Host::NumberFormatType::ShortDateTime, static_cast(login_unix_timestamp)); + m_ui.loginStatus->setText( + tr("Username: %1\nLogin token generated on %2.").arg(QString::fromStdString(username)).arg(login_timestamp)); m_ui.loginButton->setText(tr("Logout")); } else diff --git a/src/duckstation-qt/achievementsettingswidget.ui b/src/duckstation-qt/achievementsettingswidget.ui index 6de07faf9..5b9940a9c 100644 --- a/src/duckstation-qt/achievementsettingswidget.ui +++ b/src/duckstation-qt/achievementsettingswidget.ui @@ -7,7 +7,7 @@ 0 0 674 - 420 + 479 @@ -80,6 +80,13 @@ Notifications + + + + Show Leaderboard Notifications + + + @@ -165,27 +172,46 @@ - - + + - Show Leaderboard Notifications + Enable Sound Effects - + - Enable Sound Effects + Show Leaderboard Trackers - - + + + + + + + Progress Tracking + + + + - Enable In-Game Overlays + Challenge Indicators: + + + + Show Progress Indicators + + + + + +