FullscreenUI: Make notifications/toasts thread-safe

This commit is contained in:
Stenzek
2025-10-18 15:28:48 +10:00
parent 8c25144abb
commit 983cde33bc
7 changed files with 133 additions and 190 deletions

View File

@@ -1402,22 +1402,19 @@ void Achievements::DisplayAchievementSummary()
summary.assign(TRANSLATE_SV("Achievements", "This game has no achievements."));
}
GPUThread::RunOnThread([title = s_state.game_title, summary = std::string(summary.view()), icon = s_state.game_icon,
time = IsHardcoreModeActive() ? ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME_HC :
ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME]() mutable {
FullscreenUI::AddNotification("AchievementsSummary", time, std::move(title), std::move(summary), std::move(icon));
});
FullscreenUI::AddNotification("AchievementsSummary",
IsHardcoreModeActive() ? ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME_HC :
ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME,
s_state.game_title, std::string(summary), s_state.game_icon);
if (s_state.game_summary.num_unsupported_achievements > 0)
{
GPUThread::RunOnThread([num_unsupported = s_state.game_summary.num_unsupported_achievements]() mutable {
FullscreenUI::AddNotification("UnsupportedAchievements", ACHIEVEMENT_SUMMARY_UNSUPPORTED_TIME,
TRANSLATE_STR("Achievements", "Unsupported Achievements"),
TRANSLATE_PLURAL_STR("Achievements",
"%n achievements are not supported by DuckStation.",
"Achievement popup", num_unsupported),
"images/warning.svg");
});
FullscreenUI::AddNotification(
"UnsupportedAchievements", ACHIEVEMENT_SUMMARY_UNSUPPORTED_TIME,
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");
}
}
@@ -1430,11 +1427,9 @@ void Achievements::DisplayHardcoreDeferredMessage()
{
if (g_settings.achievements_hardcore_mode && System::IsValid())
{
GPUThread::RunOnThread([]() {
FullscreenUI::ShowToast(std::string(),
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
Host::OSD_WARNING_DURATION);
});
FullscreenUI::ShowToast(std::string(),
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
Host::OSD_WARNING_DURATION);
}
}
@@ -1459,14 +1454,9 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
else
title = cheevo->title;
std::string badge_path = GetAchievementBadgePath(cheevo, false);
GPUThread::RunOnThread([id = cheevo->id, duration = g_settings.achievements_notification_duration,
title = std::move(title), description = std::string(cheevo->description),
badge_path = std::move(badge_path)]() mutable {
FullscreenUI::AddNotification(fmt::format("achievement_unlock_{}", id), static_cast<float>(duration),
std::move(title), std::move(description), std::move(badge_path));
});
FullscreenUI::AddNotification(fmt::format("achievement_unlock_{}", cheevo->id),
static_cast<float>(g_settings.achievements_notification_duration), std::move(title),
cheevo->description, GetAchievementBadgePath(cheevo, false));
}
if (g_settings.achievements_sound_effects)
@@ -1486,11 +1476,8 @@ void Achievements::HandleGameCompleteEvent(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));
GPUThread::RunOnThread(
[title = s_state.game_title, message = std::move(message), icon = s_state.game_icon]() mutable {
FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
std::move(message), std::move(icon));
});
FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, s_state.game_title,
std::move(message), s_state.game_icon);
}
}
@@ -1513,18 +1500,14 @@ void Achievements::HandleSubsetCompleteEvent(const rc_client_event_t* event)
DownloadImage(std::move(url), badge_path);
}
std::string title = event->subset->title;
std::string message = fmt::format(
TRANSLATE_FS("Achievements", "Subset complete.\n{0}, {1}."),
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));
GPUThread::RunOnThread(
[title = std::move(title), message = std::move(message), badge_path = std::move(badge_path)]() mutable {
FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
std::move(message), std::move(badge_path));
});
FullscreenUI::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, event->subset->title,
std::move(message), std::move(badge_path));
}
}
@@ -1534,14 +1517,9 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
if (g_settings.achievements_leaderboard_notifications)
{
std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started.");
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_state.game_icon]() mutable {
FullscreenUI::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_STARTED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
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);
}
}
@@ -1551,14 +1529,9 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
if (g_settings.achievements_leaderboard_notifications)
{
std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed.");
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_state.game_icon]() mutable {
FullscreenUI::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_FAILED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
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);
}
}
@@ -1574,7 +1547,6 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
TRANSLATE_NOOP("Achievements", "Your Value: {}{}"),
};
std::string title = event->leaderboard->title;
std::string message = fmt::format(
fmt::runtime(Host::TranslateToStringView(
"Achievements",
@@ -1582,12 +1554,9 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown",
g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)"));
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_state.game_icon]() mutable {
FullscreenUI::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), std::move(icon));
});
FullscreenUI::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
event->leaderboard->title, std::move(message), s_state.game_icon);
}
if (g_settings.achievements_sound_effects)
@@ -1616,12 +1585,9 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score),
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
icon = s_state.game_icon]() mutable {
FullscreenUI::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), std::move(icon));
});
FullscreenUI::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title),
std::move(message), s_state.game_icon);
}
}
@@ -1694,15 +1660,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)
{
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Challenge Started: {}"),
event->achievement->title ? event->achievement->title : "");
GPUThread::RunOnThread(
[title = std::move(title),
description = std::string(event->achievement->description ? event->achievement->description : ""), badge_path,
id = event->achievement->id]() mutable {
FullscreenUI::AddNotification(fmt::format("AchievementChallenge{}", id), CHALLENGE_STARTED_NOTIFICATION_TIME,
std::move(title), std::move(description), std::move(badge_path));
});
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));
}
s_state.active_challenge_indicators.push_back(
@@ -1728,16 +1690,12 @@ void Achievements::HandleAchievementChallengeIndicatorHideEvent(const rc_client_
if (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Notification &&
event->achievement->state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE)
{
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Challenge Failed: {}"),
event->achievement->title ? event->achievement->title : "");
std::string badge_path = GetAchievementBadgePath(event->achievement, false);
GPUThread::RunOnThread(
[title = std::move(title),
description = std::string(event->achievement->description ? event->achievement->description : ""),
badge_path = std::move(badge_path), id = event->achievement->id]() mutable {
FullscreenUI::AddNotification(fmt::format("AchievementChallenge{}", id), CHALLENGE_FAILED_NOTIFICATION_TIME,
std::move(title), std::move(description), std::move(badge_path));
});
FullscreenUI::AddNotification(fmt::format("AchievementChallenge{}", event->achievement->id),
CHALLENGE_FAILED_NOTIFICATION_TIME,
fmt::format(TRANSLATE_FS("Achievements", "Challenge Failed: {}"),
event->achievement->title ? event->achievement->title : ""),
event->achievement->description ? event->achievement->description : "",
GetAchievementBadgePath(event->achievement, false));
}
if (g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Notification ||
g_settings.achievements_challenge_indicator_mode == AchievementChallengeIndicatorMode::Disabled)
@@ -1810,24 +1768,20 @@ void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
{
WARNING_LOG("Server disconnected.");
GPUThread::RunOnThread([]() {
FullscreenUI::ShowToast(
TRANSLATE_STR("Achievements", "Achievements Disconnected"),
TRANSLATE_STR("Achievements",
"An unlock request could not be completed. We will keep retrying to submit this request."),
Host::OSD_ERROR_DURATION);
});
FullscreenUI::ShowToast(
TRANSLATE_STR("Achievements", "Achievements Disconnected"),
TRANSLATE_STR("Achievements",
"An unlock request could not be completed. We will keep retrying to submit this request."),
Host::OSD_ERROR_DURATION);
}
void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
{
WARNING_LOG("Server reconnected.");
GPUThread::RunOnThread([]() {
FullscreenUI::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
Host::OSD_INFO_DURATION);
});
FullscreenUI::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
Host::OSD_INFO_DURATION);
}
void Achievements::EnableHardcodeMode(bool display_message, bool display_game_summary)
@@ -1859,12 +1813,10 @@ void Achievements::OnHardcoreModeChanged(bool enabled, bool display_message, boo
if (System::IsValid() && display_message)
{
GPUThread::RunOnThread([enabled]() {
FullscreenUI::ShowToast(std::string(),
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
Host::OSD_INFO_DURATION);
});
FullscreenUI::ShowToast(std::string(),
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
Host::OSD_INFO_DURATION);
}
if (HasActiveGame() && display_game_summary)
@@ -2149,18 +2101,13 @@ void Achievements::ClientLoginWithTokenCallback(int result, const char* error_me
// only display user error if they've started a game
if (System::IsValid())
{
std::string message = fmt::format(
TRANSLATE_FS("Achievements", "Achievement unlocks will not be submitted for this session.\nError: {}"),
error_message);
GPUThread::RunOnThread([message = std::move(message)]() mutable {
// Only display this notification if we're booting a game.
if (!GPUThread::HasGPUBackend())
return;
FullscreenUI::AddNotification("AchievementsLoginFailed", Host::OSD_ERROR_DURATION,
TRANSLATE_STR("Achievements", "RetroAchievements Login Failed"),
std::move(message), "images/warning.svg");
});
FullscreenUI::AddNotification(
"AchievementsLoginFailed", Host::OSD_ERROR_DURATION,
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;
@@ -2195,18 +2142,12 @@ void Achievements::ShowLoginNotification()
if (g_settings.achievements_notifications)
{
std::string badge_path = GetLoggedInUserBadgePath();
std::string title = user->display_name;
//: Summary for login notification.
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"),
user->score, user->score_softcore, user->num_unread_messages);
GPUThread::RunOnThread(
[title = std::move(title), summary = std::move(summary), badge_path = std::move(badge_path)]() mutable {
FullscreenUI::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title),
std::move(summary), std::move(badge_path));
});
FullscreenUI::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, user->display_name, std::move(summary),
GetLoggedInUserBadgePath());
}
}
@@ -4179,16 +4120,8 @@ void Achievements::RefreshAllProgressCallback(int result, const char* error_mess
Host::OnAchievementsAllProgressRefreshed();
if (FullscreenUI::IsInitialized())
{
GPUThread::RunOnThread([]() {
if (!FullscreenUI::IsInitialized())
return;
FullscreenUI::ShowToast({}, TRANSLATE_STR("Achievements", "Updated achievement progress database."),
Host::OSD_INFO_DURATION);
});
}
FullscreenUI::ShowToast({}, TRANSLATE_STR("Achievements", "Updated achievement progress database."),
Host::OSD_INFO_DURATION);
}
void Achievements::BuildHashDatabase(const rc_client_hash_library_t* hashlib,

View File

@@ -486,6 +486,8 @@ void FullscreenUI::Initialize()
{
UpdateRunIdleState();
}
INFO_LOG("Fullscreen UI initialized.");
}
bool FullscreenUI::IsInitialized()
@@ -512,8 +514,7 @@ void FullscreenUI::CheckForConfigChanges(const GPUSettings& old_settings)
void FullscreenUI::UpdateRunIdleState()
{
const bool new_run_idle = (HasActiveWindow() || HasToast() || HasAnyNotifications());
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::FullscreenUIActive, new_run_idle);
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::FullscreenUIActive, HasActiveWindow());
}
void FullscreenUI::OnSystemStarting()
@@ -8704,10 +8705,8 @@ void FullscreenUI::OpenAchievementsWindow()
const auto lock = Achievements::GetLock();
if (!Achievements::IsActive() || !Achievements::HasAchievements())
{
GPUThread::RunOnThread([]() {
ShowToast(std::string(), Achievements::IsActive() ? FSUI_STR("This game has no achievements.") :
FSUI_STR("Achievements are not enabled."));
});
ShowToast(std::string(), Achievements::IsActive() ? FSUI_STR("This game has no achievements.") :
FSUI_STR("Achievements are not enabled."));
return;
}
@@ -8740,10 +8739,8 @@ void FullscreenUI::OpenLeaderboardsWindow()
const auto lock = Achievements::GetLock();
if (!Achievements::IsActive() || !Achievements::HasLeaderboards())
{
GPUThread::RunOnThread([]() {
ShowToast(std::string(), Achievements::IsActive() ? FSUI_STR("This game has no leaderboards.") :
FSUI_STR("Achievements are not enabled."));
});
ShowToast(std::string(), Achievements::IsActive() ? FSUI_STR("This game has no leaderboards.") :
FSUI_STR("Achievements are not enabled."));
return;
}

View File

@@ -67,8 +67,13 @@ static void UpdateLoadingScreenProgress(s32 progress_min, s32 progress_max, s32
static bool GetLoadingScreenTimeEstimate(SmallString& out_str);
static void DrawLoadingScreen(std::string_view image, std::string_view title, std::string_view caption,
s32 progress_min, s32 progress_max, s32 progress_value, bool is_persistent);
// Returns true if any overlay windows are active, such as notifications or toasts.
static bool AreAnyNotificationsActive();
static void OnNotificationsActivated();
static void DrawNotifications(ImVec2& position, float spacing);
static void DrawToast();
static ImGuiID GetBackgroundProgressID(std::string_view str_id);
static constexpr std::array s_theme_display_names = {
@@ -296,6 +301,7 @@ struct ALIGN_TO_CACHE_LINE WidgetsState
{
std::recursive_mutex shared_state_mutex;
bool has_initialized = false; // used to prevent notification queuing without GPU device
CloseButtonState close_button_state = CloseButtonState::None;
FocusResetType focus_reset_queued = FocusResetType::None;
TransitionState transition_state = TransitionState::Inactive;
@@ -378,6 +384,7 @@ bool FullscreenUI::InitializeWidgets(Error* error)
{
std::unique_lock lock(s_state.shared_state_mutex);
s_state.has_initialized = true;
s_state.focus_reset_queued = FocusResetType::ViewChanged;
s_state.close_button_state = CloseButtonState::None;
@@ -391,7 +398,6 @@ bool FullscreenUI::InitializeWidgets(Error* error)
UpdateWidgetsSettings();
ResetMenuButtonFrame();
return true;
}
@@ -411,6 +417,8 @@ void FullscreenUI::ShutdownWidgets(bool clear_state)
s_state.texture_cache.Clear();
s_state.has_initialized = false;
if (clear_state)
{
s_state.fullscreen_footer_icon_mapping = {};
@@ -1072,6 +1080,10 @@ void FullscreenUI::EndFixedPopupDialog()
void FullscreenUI::RenderOverlays()
{
std::unique_lock lock(s_state.shared_state_mutex);
if (!AreAnyNotificationsActive())
return;
const float margin = std::max(ImGuiManager::GetScreenMargin(), LayoutScale(10.0f));
const float spacing = LayoutScale(10.0f);
const float notification_vertical_pos = GetNotificationVerticalPosition();
@@ -1080,6 +1092,10 @@ void FullscreenUI::RenderOverlays()
DrawBackgroundProgressDialogs(position, spacing);
DrawNotifications(position, spacing);
DrawToast();
// cleared?
if (!AreAnyNotificationsActive())
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::NotificationsActive, false);
}
void FullscreenUI::PushResetLayout()
@@ -3263,12 +3279,6 @@ void FullscreenUI::FileSelectorDialog::PopulateItems()
{
for (std::string& root_path : FileSystem::GetRootDirectoryList())
{
#ifdef _WIN32A
// Remove trailing backslash on Windows.
while (!root_path.empty() && root_path.back() == FS_OSPATH_SEPARATOR_CHARACTER)
root_path.pop_back();
#endif
std::string label = fmt::format(ICON_EMOJI_FILE_FOLDER " {}", root_path);
m_items.emplace_back(std::move(label), std::move(root_path), false);
}
@@ -4040,6 +4050,7 @@ void FullscreenUI::OpenBackgroundProgressDialog(std::string_view str_id, std::st
const ImGuiID id = GetBackgroundProgressID(str_id);
std::unique_lock lock(s_state.shared_state_mutex);
const bool was_active = AreAnyNotificationsActive();
#if defined(_DEBUG) || defined(_DEVEL)
for (const BackgroundProgressDialogData& data : s_state.background_progress_dialogs)
@@ -4055,6 +4066,9 @@ void FullscreenUI::OpenBackgroundProgressDialog(std::string_view str_id, std::st
data.max = max;
data.value = value;
s_state.background_progress_dialogs.push_back(std::move(data));
if (!was_active)
OnNotificationsActivated();
}
void FullscreenUI::UpdateBackgroundProgressDialog(std::string_view str_id, std::string message, s32 min, s32 max,
@@ -4083,7 +4097,7 @@ void FullscreenUI::CloseBackgroundProgressDialog(std::string_view str_id)
{
const ImGuiID id = GetBackgroundProgressID(str_id);
std::unique_lock lock(s_state.shared_state_mutex);
const std::unique_lock lock(s_state.shared_state_mutex);
for (auto it = s_state.background_progress_dialogs.begin(); it != s_state.background_progress_dialogs.end(); ++it)
{
@@ -4114,7 +4128,6 @@ bool FullscreenUI::IsBackgroundProgressDialogOpen(std::string_view str_id)
void FullscreenUI::DrawBackgroundProgressDialogs(ImVec2& position, float spacing)
{
std::unique_lock lock(s_state.shared_state_mutex);
if (s_state.background_progress_dialogs.empty())
return;
@@ -4591,9 +4604,28 @@ bool LoadingScreenProgressCallback::ModalConfirmation(const std::string_view mes
static constexpr float NOTIFICATION_APPEAR_ANIMATION_TIME = 0.2f;
static constexpr float NOTIFICATION_DISAPPEAR_ANIMATION_TIME = 0.5f;
bool FullscreenUI::AreAnyNotificationsActive()
{
return (!s_state.notifications.empty() || !s_state.toast_title.empty() || !s_state.toast_message.empty() ||
!s_state.background_progress_dialogs.empty());
}
void FullscreenUI::OnNotificationsActivated()
{
if (GPUThread::IsOnThread())
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::NotificationsActive, true);
else
GPUThread::RunOnThread([]() { GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::NotificationsActive, true); });
}
void FullscreenUI::AddNotification(std::string key, float duration, std::string title, std::string text,
std::string image_path)
{
const std::unique_lock lock(s_state.shared_state_mutex);
if (!s_state.has_initialized)
return;
const bool prev_had_notifications = AreAnyNotificationsActive();
const Timer::Value current_time = Timer::GetCurrentValue();
if (!key.empty())
@@ -4627,17 +4659,9 @@ void FullscreenUI::AddNotification(std::string key, float duration, std::string
notif.target_y = -1.0f;
notif.last_y = -1.0f;
s_state.notifications.push_back(std::move(notif));
UpdateRunIdleState();
}
bool FullscreenUI::HasAnyNotifications()
{
return !s_state.notifications.empty();
}
void FullscreenUI::ClearNotifications()
{
s_state.notifications.clear();
if (!prev_had_notifications)
OnNotificationsActivated();
}
void FullscreenUI::DrawNotifications(ImVec2& position, float spacing)
@@ -4773,33 +4797,22 @@ void FullscreenUI::DrawNotifications(ImVec2& position, float spacing)
position.y += s_notification_vertical_direction * (box_height + shadow_size + spacing);
index++;
}
// all gone?
if (s_state.notifications.empty())
FullscreenUI::UpdateRunIdleState();
}
void FullscreenUI::ShowToast(std::string title, std::string message, float duration)
{
const std::unique_lock lock(s_state.shared_state_mutex);
if (!s_state.has_initialized)
return;
const bool prev_had_notifications = AreAnyNotificationsActive();
s_state.toast_title = std::move(title);
s_state.toast_message = std::move(message);
s_state.toast_start_time = Timer::GetCurrentValue();
s_state.toast_duration = duration;
FullscreenUI::UpdateRunIdleState();
}
bool FullscreenUI::HasToast()
{
return (!s_state.toast_title.empty() || !s_state.toast_message.empty());
}
void FullscreenUI::ClearToast()
{
s_state.toast_message = {};
s_state.toast_title = {};
s_state.toast_start_time = 0;
s_state.toast_duration = 0.0f;
FullscreenUI::UpdateRunIdleState();
if (!prev_had_notifications)
OnNotificationsActivated();
}
void FullscreenUI::DrawToast()
@@ -4811,7 +4824,10 @@ void FullscreenUI::DrawToast()
static_cast<float>(Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - s_state.toast_start_time));
if (elapsed >= s_state.toast_duration)
{
ClearToast();
s_state.toast_message = {};
s_state.toast_title = {};
s_state.toast_start_time = 0;
s_state.toast_duration = 0.0f;
return;
}

View File

@@ -491,12 +491,7 @@ void CloseLoadingScreen();
void RenderLoadingScreen();
void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path);
bool HasAnyNotifications();
void ClearNotifications();
void ShowToast(std::string title, std::string message, float duration = 10.0f);
bool HasToast();
void ClearToast();
// Wrapper for an animated popup dialog.
class PopupDialog

View File

@@ -1472,8 +1472,9 @@ void GPUThread::UpdateRunIdle()
static constexpr u8 REQUIRE_MASK = static_cast<u8>(RunIdleReason::NoGPUBackend) |
static_cast<u8>(RunIdleReason::SystemPaused) |
static_cast<u8>(RunIdleReason::LoadingScreenActive);
static constexpr u8 ACTIVATE_MASK =
static_cast<u8>(RunIdleReason::FullscreenUIActive) | static_cast<u8>(RunIdleReason::LoadingScreenActive);
static constexpr u8 ACTIVATE_MASK = static_cast<u8>(RunIdleReason::FullscreenUIActive) |
static_cast<u8>(RunIdleReason::NotificationsActive) |
static_cast<u8>(RunIdleReason::LoadingScreenActive);
const bool new_flag = (g_gpu_device && ((s_state.run_idle_reasons & REQUIRE_MASK) != 0) &&
((s_state.run_idle_reasons & ACTIVATE_MASK) != 0));

View File

@@ -36,7 +36,8 @@ enum class RunIdleReason : u8
NoGPUBackend = (1 << 0),
SystemPaused = (1 << 1),
FullscreenUIActive = (1 << 2),
LoadingScreenActive = (1 << 3),
NotificationsActive = (1 << 3),
LoadingScreenActive = (1 << 4),
};
/// Starts Big Picture UI.

View File

@@ -542,7 +542,7 @@ void ImGuiManager::UpdateTextures()
GPUTexture* const gtex = reinterpret_cast<GPUTexture*>(tex->GetTexID());
for (const ImTextureRect& rc : tex->Updates)
{
DEV_LOG("Update {}x{} @ {},{} in {}x{} ImGui texture", rc.w, rc.h, rc.x, rc.y, tex->Width, tex->Height);
DEBUG_LOG("Update {}x{} @ {},{} in {}x{} ImGui texture", rc.w, rc.h, rc.x, rc.y, tex->Width, tex->Height);
if (!gtex->Update(rc.x, rc.y, rc.w, rc.h, tex->GetPixelsAt(rc.x, rc.y), tex->GetPitch())) [[unlikely]]
{
ERROR_LOG("Failed to update {}x{} rect @ {},{} in imgui texture", rc.w, rc.h, rc.x, rc.y);