mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
FullscreenUI: Make notifications/toasts thread-safe
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user