diff --git a/pcsx2-qt/SettingWidgetBinder.h b/pcsx2-qt/SettingWidgetBinder.h index 17b300b5a6..7aa0c3516f 100644 --- a/pcsx2-qt/SettingWidgetBinder.h +++ b/pcsx2-qt/SettingWidgetBinder.h @@ -1078,7 +1078,10 @@ namespace SettingWidgetBinder static inline void BindSliderToIntSetting(SettingsInterface* sif, QSlider* slider, QLabel* label, const QString& label_suffix, std::string section, std::string key, s32 default_value) { - const s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value); + s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value); + + //Clamp in case setting was updated manually using INI + global_value = std::clamp(global_value, slider->minimum(), slider->maximum()); if (sif) { @@ -1086,7 +1089,9 @@ namespace SettingWidgetBinder QFont bold_font(orig_font); bold_font.setBold(true); - const s32 current_value = sif->GetOptionalIntValue(section.c_str(), key.c_str()).value_or(global_value); + s32 current_value = sif->GetOptionalIntValue(section.c_str(), key.c_str()).value_or(global_value); + current_value = std::clamp(current_value, slider->minimum(), slider->maximum()); + slider->setValue(current_value); label->setText(QStringLiteral("%1%2").arg(current_value).arg(label_suffix)); @@ -1100,8 +1105,10 @@ namespace SettingWidgetBinder [sif, slider, label, label_suffix, orig_font = std::move(orig_font), section, key, default_value](const QPoint& pt) { QMenu menu(slider); slider->connect(menu.addAction(qApp->translate("SettingWidgetBinder", "Reset")), &QAction::triggered, slider, - [sif, label, label_suffix, orig_font, section, key, default_value]() { - const s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value); + [sif, slider, label, label_suffix, orig_font, section, key, default_value]() { + s32 global_value = Host::GetBaseIntSettingValue(section.c_str(), key.c_str(), default_value); + global_value = std::clamp(global_value, slider->minimum(), slider->maximum()); + label->setText(QStringLiteral("%1%2").arg(global_value).arg(label_suffix)); label->setFont(orig_font); diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.cpp b/pcsx2-qt/Settings/AchievementSettingsWidget.cpp index db1b09fab1..ea38f2f8d8 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.cpp +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.cpp @@ -30,6 +30,8 @@ #include #include +static constexpr s32 DEFAULT_NOTIFICATIONS_DURATION = 5; + AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent) : QWidget(parent) , m_dialog(dialog) @@ -48,6 +50,9 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWi SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Achievements", "SoundEffects", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.primedIndicators, "Achievements", "PrimedIndicators", true); + SettingWidgetBinder::BindSliderToIntSetting(sif, m_ui.notifications_duration, m_ui.notifications_duration_seconds, + tr(" seconds"), "Achievements", "NotificationsDuration", DEFAULT_NOTIFICATIONS_DURATION); + dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, PCSX2 will scan for achievements on game load.")); dialog->registerWidgetHelp(m_ui.testMode, tr("Enable Test Mode"), tr("Unchecked"), @@ -70,10 +75,18 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWi dialog->registerWidgetHelp(m_ui.primedIndicators, tr("Show Challenge Indicators"), tr("Checked"), tr("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active.")); + dialog->registerWidgetHelp(m_ui.notifications_duration, tr("Notification Duration"), + tr("5 seconds"), tr("The duration, in seconds, an achievement popup notification will remain on screen.")); + dialog->registerWidgetHelp(m_ui.notifications_duration_label, tr("Notification Duration"), + tr("5 seconds"), tr("The duration, in seconds, an achievement popup notification will remain on screen.")); + dialog->registerWidgetHelp(m_ui.notifications_duration_seconds, tr("Notification Duration"), + tr("5 seconds"), tr("The duration, in seconds, an achievement popup notification will remain on screen.")); + connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.notifications, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::onChallengeModeStateChanged); + connect(m_ui.notifications_duration, &QSlider::valueChanged, this, &AchievementSettingsWidget::onNotificationsDurationChanged); if (!m_dialog->isPerGameSettings()) { @@ -105,6 +118,8 @@ void AchievementSettingsWidget::updateEnableState() { const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false); const bool challenge = m_dialog->getEffectiveBoolValue("Achievements", "ChallengeMode", false); + const bool notifications = m_dialog->getEffectiveBoolValue("Achievements", "Notifications", true); + m_ui.testMode->setEnabled(enabled); m_ui.unofficialTestMode->setEnabled(enabled); m_ui.richPresence->setEnabled(enabled); @@ -113,6 +128,10 @@ void AchievementSettingsWidget::updateEnableState() m_ui.notifications->setEnabled(enabled); m_ui.soundEffects->setEnabled(enabled); m_ui.primedIndicators->setEnabled(enabled); + + m_ui.notifications_duration->setEnabled(enabled && notifications); + m_ui.notifications_duration_label->setEnabled(enabled && notifications); + m_ui.notifications_duration_seconds->setEnabled(enabled && notifications); } void AchievementSettingsWidget::onChallengeModeStateChanged() @@ -196,3 +215,9 @@ void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QStrin { m_ui.gameInfo->setText(game_info_string); } + +void AchievementSettingsWidget::onNotificationsDurationChanged() +{ + m_ui.notifications_duration_seconds->setText(tr("%1 seconds") + .arg(m_ui.notifications_duration->value())); +} \ No newline at end of file diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.h b/pcsx2-qt/Settings/AchievementSettingsWidget.h index 87ff075e84..70505ee6ee 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.h +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.h @@ -33,6 +33,7 @@ private Q_SLOTS: void onLoginLogoutPressed(); void onViewProfilePressed(); void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points); + void onNotificationsDurationChanged(); private: void updateLoginState(); diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.ui b/pcsx2-qt/Settings/AchievementSettingsWidget.ui index 54b522072a..0950eeeed9 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.ui +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.ui @@ -6,8 +6,8 @@ 0 0 - 658 - 496 + 829 + 641 @@ -39,13 +39,6 @@ - - - - Show Challenge Indicators - - - @@ -74,13 +67,6 @@ - - - - Enable Test Mode - - - @@ -89,12 +75,93 @@ + + + Show Challenge Indicators + + + + + + + Enable Test Mode + + + + + + + + + + Notifications + + + Show Notifications + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Duration + + + + + + + 3 + + + 10 + + + 1 + + + 5 + + + Qt::Horizontal + + + false + + + QSlider::TicksBelow + + + 1 + + + + + + + 5 seconds + + + + + diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index 963d26bb75..c8b5aab83e 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -132,6 +132,8 @@ namespace Achievements static void UnlockAchievementCallback(s32 status_code, const std::string& content_type, Common::HTTPDownloader::Request::Data data); static void SubmitLeaderboardCallback(s32 status_code, const std::string& content_type, Common::HTTPDownloader::Request::Data data, u32 lboard_id); + static s32 GetNotificationsDuration(); + static bool s_active = false; static bool s_logged_in = false; static bool s_challenge_mode = false; @@ -1117,9 +1119,9 @@ void Achievements::DisplayAchievementSummary() summary.append(TRANSLATE_SV("Achievements", "Leaderboard submission is enabled.")); } - MTGS::RunOnGSThread([title = std::move(title), summary = std::move(summary), icon = s_game_icon]() { + MTGS::RunOnGSThread([duration = GetNotificationsDuration(), title = std::move(title), summary = std::move(summary), icon = s_game_icon]() { if (FullscreenUI::IsInitialized()) - ImGuiFullscreen::AddNotification(10.0f, std::move(title), std::move(summary), std::move(icon)); + ImGuiFullscreen::AddNotification(duration, std::move(title), std::move(summary), std::move(icon)); }); } @@ -1137,9 +1139,9 @@ void Achievements::DisplayMasteredNotification() std::string message(fmt::format( "{} achievements, {} points{}", GetAchievementCount(), GetCurrentPointsForGame(), s_challenge_mode ? " (Hardcore Mode)" : "")); - MTGS::RunOnGSThread([title = std::move(title), message = std::move(message), icon = s_game_icon]() { + MTGS::RunOnGSThread([duration = GetNotificationsDuration(), title = std::move(title), message = std::move(message), icon = s_game_icon]() { if (FullscreenUI::IsInitialized()) - ImGuiFullscreen::AddNotification(20.0f, std::move(title), std::move(message), std::move(icon)); + ImGuiFullscreen::AddNotification(duration, std::move(title), std::move(message), std::move(icon)); }); } @@ -1850,9 +1852,9 @@ void Achievements::SubmitLeaderboardCallback(s32 status_code, const std::string& std::string summary = fmt::format( "Your Score: {} (Best: {})\nLeaderboard Position: {} of {}", submitted_score, best_score, response.new_rank, response.num_entries); - MTGS::RunOnGSThread([title = lb->title, summary = std::move(summary), icon = s_game_icon]() { + MTGS::RunOnGSThread([duration = GetNotificationsDuration(), title = lb->title, summary = std::move(summary), icon = s_game_icon]() { if (FullscreenUI::IsInitialized()) - ImGuiFullscreen::AddNotification(10.0f, std::move(title), std::move(summary), std::move(icon)); + ImGuiFullscreen::AddNotification(duration, std::move(title), std::move(summary), std::move(icon)); }); } @@ -1894,8 +1896,8 @@ void Achievements::UnlockAchievement(u32 achievement_id, bool add_notification / } MTGS::RunOnGSThread( - [title = std::move(title), description = achievement->description, icon = GetAchievementBadgePath(*achievement)]() { - ImGuiFullscreen::AddNotification(15.0f, std::move(title), std::move(description), std::move(icon)); + [duration = GetNotificationsDuration(), title = std::move(title), description = achievement->description, icon = GetAchievementBadgePath(*achievement)]() { + ImGuiFullscreen::AddNotification(duration, std::move(title), std::move(description), std::move(icon)); }); } @@ -2153,6 +2155,12 @@ void Achievements::PokeMemory(unsigned address, unsigned num_bytes, void* ud, un } } + +s32 Achievements::GetNotificationsDuration() +{ + return EmuConfig.Achievements.NotificationsDuration; +} + #ifdef ENABLE_RAINTEGRATION #include "RA_Consoles.h" diff --git a/pcsx2/Config.h b/pcsx2/Config.h index e0f8a953c4..fc7dd05b33 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -1262,12 +1262,14 @@ struct Pcsx2Config PrimedIndicators : 1; BITFIELD_END + s32 NotificationsDuration = 5; + AchievementsOptions(); void LoadSave(SettingsWrapper& wrap); bool operator==(const AchievementsOptions& right) const { - return OpEqu(bitset); + return OpEqu(bitset) && OpEqu(NotificationsDuration); } bool operator!=(const AchievementsOptions& right) const diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index e36d80796c..641ed69101 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -1429,6 +1429,7 @@ Pcsx2Config::AchievementsOptions::AchievementsOptions() Notifications = true; SoundEffects = true; PrimedIndicators = true; + NotificationsDuration = 5; } void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap) @@ -1444,6 +1445,13 @@ void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(Notifications); SettingsWrapBitBool(SoundEffects); SettingsWrapBitBool(PrimedIndicators); + SettingsWrapBitfield(NotificationsDuration); + + if (wrap.IsLoading()) + { + //Clamp in case setting was updated manually using the INI + NotificationsDuration = std::clamp(NotificationsDuration, 3, 10); + } } #endif