From 5818a05be3bc5773a3e598bfb86a68492c10b983 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 25 Oct 2025 13:53:03 +1000 Subject: [PATCH] Achievements: Add point count to unlock popup --- src/core/achievements.cpp | 64 ++++++++++++++++--------------- src/core/fullscreenui_widgets.cpp | 46 ++++++++++++++-------- src/core/fullscreenui_widgets.h | 3 +- 3 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index c4e71cf86..29ab107b0 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -1263,16 +1263,16 @@ void Achievements::DisplayAchievementSummary() FullscreenUI::AddNotification("AchievementsSummary", IsHardcoreModeActive() ? ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME_HC : ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, - s_state.game_title, std::string(summary), s_state.game_icon); + s_state.game_icon, s_state.game_title, std::string(summary), {}); if (s_state.game_summary.num_unsupported_achievements > 0) { FullscreenUI::AddNotification( - "UnsupportedAchievements", ACHIEVEMENT_SUMMARY_UNSUPPORTED_TIME, + "UnsupportedAchievements", ACHIEVEMENT_SUMMARY_UNSUPPORTED_TIME, "images/warning.svg", TRANSLATE_STR("Achievements", "Unsupported Achievements"), TRANSLATE_PLURAL_STR("Achievements", "%n achievements are not supported by DuckStation.", "Achievement popup", s_state.game_summary.num_unsupported_achievements), - "images/warning.svg"); + {}); } } @@ -1312,9 +1312,12 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event) else title = cheevo->title; + std::string note = fmt::format(ICON_EMOJI_TROPHY " {}", cheevo->points); + FullscreenUI::AddNotification(fmt::format("achievement_unlock_{}", cheevo->id), - static_cast(g_settings.achievements_notification_duration), std::move(title), - cheevo->description, GetAchievementBadgePath(cheevo, false)); + static_cast(g_settings.achievements_notification_duration), + GetAchievementBadgePath(cheevo, false), std::move(title), + std::string(cheevo->description), std::move(note)); } if (g_settings.achievements_sound_effects) @@ -1333,9 +1336,10 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event) TRANSLATE_PLURAL_STR("Achievements", "%n achievements", "Mastery popup", s_state.game_summary.num_unlocked_achievements), TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked)); + std::string note = fmt::format(ICON_EMOJI_TROPHY " {}", s_state.game_summary.points_unlocked); - FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, s_state.game_title, - std::move(message), s_state.game_icon); + FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, s_state.game_icon, + s_state.game_title, std::move(message), std::move(note)); } } @@ -1364,8 +1368,8 @@ void Achievements::HandleSubsetCompleteEvent(const rc_client_event_t* event) s_state.game_summary.num_unlocked_achievements), TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked)); - FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, event->subset->title, - std::move(message), std::move(badge_path)); + FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(badge_path), + std::string(event->subset->title), std::move(message), {}); } } @@ -1375,9 +1379,9 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event) if (g_settings.achievements_leaderboard_notifications) { - FullscreenUI::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), - LEADERBOARD_STARTED_NOTIFICATION_TIME, event->leaderboard->title, - TRANSLATE_STR("Achievements", "Leaderboard attempt started."), s_state.game_icon); + FullscreenUI::AddNotification( + fmt::format("leaderboard_{}", event->leaderboard->id), LEADERBOARD_STARTED_NOTIFICATION_TIME, s_state.game_icon, + std::string(event->leaderboard->title), TRANSLATE_STR("Achievements", "Leaderboard attempt started."), {}); } } @@ -1387,9 +1391,9 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event) if (g_settings.achievements_leaderboard_notifications) { - FullscreenUI::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), - LEADERBOARD_FAILED_NOTIFICATION_TIME, event->leaderboard->title, - TRANSLATE_STR("Achievements", "Leaderboard attempt failed."), s_state.game_icon); + FullscreenUI::AddNotification( + fmt::format("leaderboard_{}", event->leaderboard->id), LEADERBOARD_FAILED_NOTIFICATION_TIME, s_state.game_icon, + std::string(event->leaderboard->title), TRANSLATE_STR("Achievements", "Leaderboard attempt failed."), {}); } } @@ -1413,8 +1417,8 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)")); FullscreenUI::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), - static_cast(g_settings.achievements_leaderboard_duration), - event->leaderboard->title, std::move(message), s_state.game_icon); + static_cast(g_settings.achievements_leaderboard_duration), s_state.game_icon, + std::string(event->leaderboard->title), std::move(message), {}); } if (g_settings.achievements_sound_effects) @@ -1444,8 +1448,8 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries); FullscreenUI::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), - static_cast(g_settings.achievements_leaderboard_duration), std::move(title), - std::move(message), s_state.game_icon); + static_cast(g_settings.achievements_leaderboard_duration), s_state.game_icon, + std::move(title), std::move(message), {}); } } @@ -1518,11 +1522,11 @@ void Achievements::HandleAchievementChallengeIndicatorShowEvent(const rc_client_ // 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) { - FullscreenUI::AddNotification( - fmt::format("AchievementChallenge{}", event->achievement->id), CHALLENGE_STARTED_NOTIFICATION_TIME, - fmt::format(TRANSLATE_FS("Achievements", "Challenge Started: {}"), - event->achievement->title ? event->achievement->title : ""), - event->achievement->description ? event->achievement->description : "", std::move(badge_path)); + FullscreenUI::AddNotification(fmt::format("AchievementChallenge{}", event->achievement->id), + CHALLENGE_STARTED_NOTIFICATION_TIME, std::move(badge_path), + fmt::format(TRANSLATE_FS("Achievements", "Challenge Started: {}"), + event->achievement->title ? event->achievement->title : ""), + event->achievement->description, {}); } s_state.active_challenge_indicators.push_back( @@ -1549,10 +1553,10 @@ void Achievements::HandleAchievementChallengeIndicatorHideEvent(const rc_client_ { FullscreenUI::AddNotification(fmt::format("AchievementChallenge{}", event->achievement->id), CHALLENGE_FAILED_NOTIFICATION_TIME, + GetAchievementBadgePath(event->achievement, false), fmt::format(TRANSLATE_FS("Achievements", "Challenge Failed: {}"), event->achievement->title ? event->achievement->title : ""), - event->achievement->description ? event->achievement->description : "", - GetAchievementBadgePath(event->achievement, false)); + event->achievement->description, {}); } if (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Notification || g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Disabled) @@ -1946,12 +1950,12 @@ void Achievements::ClientLoginWithTokenCallback(int result, const char* error_me if (System::IsValid()) { FullscreenUI::AddNotification( - "AchievementsLoginFailed", Host::OSD_ERROR_DURATION, + "AchievementsLoginFailed", Host::OSD_ERROR_DURATION, "images/warning.svg", TRANSLATE_STR("Achievements", "RetroAchievements Login Failed"), fmt::format( TRANSLATE_FS("Achievements", "Achievement unlocks will not be submitted for this session.\nError: {}"), error_message), - "images/warning.svg"); + {}); } return; @@ -1990,8 +1994,8 @@ void Achievements::FinishLogin() std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"), user->score, user->score_softcore, user->num_unread_messages); - FullscreenUI::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, user->display_name, std::move(summary), - s_state.user_badge_path); + FullscreenUI::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, s_state.user_badge_path, + user->display_name, std::move(summary), {}); } } diff --git a/src/core/fullscreenui_widgets.cpp b/src/core/fullscreenui_widgets.cpp index 91f76c793..0e28d40c7 100644 --- a/src/core/fullscreenui_widgets.cpp +++ b/src/core/fullscreenui_widgets.cpp @@ -139,6 +139,7 @@ struct Notification std::string key; std::string title; std::string text; + std::string note; std::string badge_path; Timer::Value start_time; Timer::Value move_time; @@ -4695,8 +4696,8 @@ void FullscreenUI::UpdateNotificationsRunIdle() []() { GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::NotificationsActive, AreAnyNotificationsActive()); }); } -void FullscreenUI::AddNotification(std::string key, float duration, std::string title, std::string text, - std::string image_path) +void FullscreenUI::AddNotification(std::string key, float duration, std::string image_path, std::string title, + std::string text, std::string note) { const std::unique_lock lock(s_state.shared_state_mutex); if (!s_state.has_initialized) @@ -4714,6 +4715,7 @@ void FullscreenUI::AddNotification(std::string key, float duration, std::string it->duration = duration; it->title = std::move(title); it->text = std::move(text); + it->note = std::move(note); it->badge_path = std::move(image_path); // Don't fade it in again @@ -4730,6 +4732,7 @@ void FullscreenUI::AddNotification(std::string key, float duration, std::string notif.duration = duration; notif.title = std::move(title); notif.text = std::move(text); + notif.note = std::move(note); notif.badge_path = std::move(image_path); notif.start_time = current_time; notif.move_time = current_time; @@ -4761,12 +4764,13 @@ void FullscreenUI::DrawNotifications(ImVec2& position, float spacing) const float shadow_size = FullscreenUI::LayoutScale(2.0f); const float rounding = FullscreenUI::LayoutScale(20.0f); - ImFont* const title_font = UIStyle.Font; - const float title_font_size = UIStyle.LargeFontSize; - const float title_font_weight = UIStyle.BoldFontWeight; - ImFont* const text_font = UIStyle.Font; - const float text_font_size = UIStyle.MediumFontSize; - const float text_font_weight = UIStyle.NormalFontWeight; + ImFont*& font = UIStyle.Font; + const float& title_font_size = UIStyle.LargeFontSize; + const float& title_font_weight = UIStyle.BoldFontWeight; + const float& text_font_size = UIStyle.MediumFontSize; + const float& text_font_weight = UIStyle.NormalFontWeight; + const float& note_font_size = UIStyle.MediumFontSize; + const float& note_font_weight = UIStyle.BoldFontWeight; for (u32 index = 0; index < static_cast(s_state.notifications.size());) { @@ -4778,10 +4782,14 @@ void FullscreenUI::DrawNotifications(ImVec2& position, float spacing) continue; } - const ImVec2 title_size = title_font->CalcTextSizeA(title_font_size, title_font_weight, max_text_width, - max_text_width, IMSTR_START_END(notif.title)); - const ImVec2 text_size = text_font->CalcTextSizeA(text_font_size, text_font_weight, max_text_width, max_text_width, - IMSTR_START_END(notif.text)); + // place note to the right of the title + const ImVec2 note_size = notif.note.empty() ? ImVec2() : + font->CalcTextSizeA(note_font_size, note_font_weight, FLT_MAX, 0.0f, + IMSTR_START_END(notif.note)); + const ImVec2 title_size = font->CalcTextSizeA(title_font_size, title_font_weight, max_text_width - note_size.x, + max_text_width - note_size.x, IMSTR_START_END(notif.title)); + const ImVec2 text_size = font->CalcTextSizeA(text_font_size, text_font_weight, max_text_width, max_text_width, + IMSTR_START_END(notif.text)); float box_width = std::max((horizontal_padding * 2.0f) + badge_size + horizontal_spacing + ImCeil(std::max(title_size.x, text_size.x)), @@ -4860,14 +4868,22 @@ void FullscreenUI::DrawNotifications(ImVec2& position, float spacing) const ImVec2 title_pos = ImVec2(badge_max.x + horizontal_spacing, box_min.y + vertical_padding); const ImRect title_bb = ImRect(title_pos, title_pos + title_size); - RenderShadowedTextClipped(dl, title_font, title_font_size, title_font_weight, title_bb.Min, title_bb.Max, title_col, - notif.title, &title_size, ImVec2(0.0f, 0.0f), max_text_width, &title_bb); + RenderShadowedTextClipped(dl, font, title_font_size, title_font_weight, title_bb.Min, title_bb.Max, title_col, + notif.title, &title_size, ImVec2(0.0f, 0.0f), max_text_width - note_size.x, &title_bb); const ImVec2 text_pos = ImVec2(badge_max.x + horizontal_spacing, title_bb.Max.y + vertical_spacing); const ImRect text_bb = ImRect(text_pos, text_pos + text_size); - RenderShadowedTextClipped(dl, text_font, text_font_size, text_font_weight, text_bb.Min, text_bb.Max, text_col, + RenderShadowedTextClipped(dl, font, text_font_size, text_font_weight, text_bb.Min, text_bb.Max, text_col, notif.text, &text_size, ImVec2(0.0f, 0.0f), max_text_width, &text_bb); + if (!notif.note.empty()) + { + const ImVec2 note_pos = ImVec2(box_max.x - horizontal_padding - note_size.x, box_min.y + vertical_padding); + const ImRect note_bb = ImRect(note_pos, note_pos + note_size); + RenderShadowedTextClipped(dl, font, note_font_size, note_font_weight, note_bb.Min, note_bb.Max, title_col, + notif.note, ¬e_size, ImVec2(0.0f, 0.0f), max_text_width, ¬e_bb); + } + if (clip_box) dl->PopClipRect(); diff --git a/src/core/fullscreenui_widgets.h b/src/core/fullscreenui_widgets.h index 013f434e7..cff82a2ec 100644 --- a/src/core/fullscreenui_widgets.h +++ b/src/core/fullscreenui_widgets.h @@ -491,7 +491,8 @@ bool IsLoadingScreenOpen(); void CloseLoadingScreen(); /// Notification and toast support. -void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path); +void AddNotification(std::string key, float duration, std::string image_path, std::string title, std::string text, + std::string note); void ShowToast(std::string title, std::string message, float duration = 10.0f); // Wrapper for an animated popup dialog.