From 76b758dbd26c3675a19440b2aac1a1cfaa28b1c6 Mon Sep 17 00:00:00 2001 From: SternXD Date: Wed, 2 Jul 2025 14:21:17 -0400 Subject: [PATCH] OSD/Achievements: Add 9-position alignment options for Achievement notifications/popups, and OSD --- .../Settings/AchievementSettingsWidget.cpp | 16 ++ .../Settings/AchievementSettingsWidget.ui | 127 ++++++++++ pcsx2-qt/Settings/GraphicsSettingsWidget.ui | 78 +++++- pcsx2/Achievements.cpp | 230 +++++++++++++++++- pcsx2/Config.h | 25 ++ pcsx2/GSDumpReplayer.cpp | 6 +- pcsx2/ImGui/FullscreenUI.cpp | 52 ++++ pcsx2/ImGui/ImGuiFullscreen.cpp | 34 ++- pcsx2/ImGui/ImGuiFullscreen.h | 1 + pcsx2/ImGui/ImGuiManager.cpp | 54 +++- pcsx2/ImGui/ImGuiOverlays.cpp | 164 +++++++++++-- pcsx2/ImGui/ImGuiOverlays.h | 7 + pcsx2/Pcsx2Config.cpp | 17 +- 13 files changed, 769 insertions(+), 42 deletions(-) diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.cpp b/pcsx2-qt/Settings/AchievementSettingsWidget.cpp index 15b4ddec99..f62792af32 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.cpp +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.cpp @@ -9,6 +9,7 @@ #include "QtUtils.h" #include "pcsx2/Achievements.h" +#include "pcsx2/Config.h" #include "pcsx2/Host.h" #include "common/StringUtil.h" @@ -35,6 +36,8 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unlockSound, "Achievements", "UnlockSound", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.lbSound, "Achievements", "LBSubmitSound", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.overlays, "Achievements", "Overlays", true); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overlayPosition, "Achievements", "OverlayPosition", static_cast(AchievementOverlayPosition::BottomRight)); + SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.notificationPosition, "Achievements", "NotificationPosition", static_cast(OsdOverlayPos::TopLeft)); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Achievements", "EncoreMode", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Achievements", "SpectatorMode", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialAchievements, "Achievements", "UnofficialTestMode",false); @@ -52,6 +55,8 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi 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.soundEffectsBox, tr("Custom Sound Effect"), tr("Any"), tr("Customize the sound effect that are played whenever you received a notification, earned an achievement or submitted an entry to the leaderboard.")); 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.overlayPosition, tr("Overlay Position"), tr("Bottom Right"), tr("Determines where achievement overlays are positioned on the screen.")); + dialog->registerWidgetHelp(m_ui.notificationPosition, tr("Notification Position"), tr("Top Left"), tr("Determines where achievement notification popups are positioned on the screen.")); 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"), tr("When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server.")); dialog->registerWidgetHelp(m_ui.unofficialAchievements, tr("Test Unofficial Achievements"), tr("Unchecked"), tr("When enabled, PCSX2 will list achievements from unofficial sets. Please note that these achievements are not tracked by RetroAchievements, so they unlock every time.")); @@ -65,6 +70,7 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi connect(m_ui.notificationSound, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.unlockSound, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.lbSound, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); + connect(m_ui.overlays, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.achievementNotificationsDuration, &QSlider::valueChanged, this, &AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged); connect(m_ui.leaderboardNotificationsDuration, &QSlider::valueChanged, this, &AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged); @@ -139,6 +145,16 @@ void AchievementSettingsWidget::updateEnableState() m_ui.soundEffects->setEnabled(enabled); m_ui.overlays->setEnabled(enabled); + + const bool overlays_enabled = enabled && m_dialog->getEffectiveBoolValue("Achievements", "Overlays", true); + const bool notifications_enabled = enabled && (m_dialog->getEffectiveBoolValue("Achievements", "Notifications", true) || + m_dialog->getEffectiveBoolValue("Achievements", "LeaderboardNotifications", true)); + m_ui.overlaySettingsBox->setEnabled(overlays_enabled || notifications_enabled); + m_ui.overlayPosition->setEnabled(overlays_enabled); + m_ui.overlayPositionLabel->setEnabled(overlays_enabled); + m_ui.notificationPosition->setEnabled(notifications_enabled); + m_ui.notificationPositionLabel->setEnabled(notifications_enabled); + m_ui.encoreMode->setEnabled(enabled); m_ui.spectatorMode->setEnabled(enabled); m_ui.unofficialAchievements->setEnabled(enabled); diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.ui b/pcsx2-qt/Settings/AchievementSettingsWidget.ui index 1163cfbd9b..7a4fc99b67 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.ui +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.ui @@ -197,6 +197,133 @@ + + + + Overlay Settings + + + + + + Overlay Position: + + + + + + + + Top Left + + + + + Top Center + + + + + Top Right + + + + + Center Left + + + + + Center + + + + + Center Right + + + + + Bottom Left + + + + + Bottom Center + + + + + Bottom Right (Default) + + + + + + + + Notification Position: + + + + + + + + None + + + + + Top Left (Default) + + + + + Top Center + + + + + Top Right + + + + + Center Left + + + + + Center + + + + + Center Right + + + + + Bottom Left + + + + + Bottom Center + + + + + Bottom Right + + + + + + + + diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index 476a5bb962..01a45c503e 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -1612,12 +1612,47 @@ - Left (Default) + Top Left (Default) - Right + Top Center + + + + + Top Right + + + + + Center Left + + + + + Center + + + + + Center Right + + + + + Bottom Left + + + + + Bottom Center + + + + + Bottom Right @@ -1631,12 +1666,47 @@ - Left + Top Left - Right (Default) + Top Center + + + + + Top Right (Default) + + + + + Center Left + + + + + Center + + + + + Center Right + + + + + Bottom Left + + + + + Bottom Center + + + + + Bottom Right diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index 67c7e414b5..e2eac53000 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -164,6 +164,7 @@ namespace Achievements static void DisplayHardcoreDeferredMessage(); static void DisplayAchievementSummary(); static void UpdateRichPresence(std::unique_lock& lock); + static void UpdateNotificationPosition(); static std::string GetAchievementBadgePath(const rc_client_achievement_t* achievement, int state); static std::string GetUserBadgePath(const std::string_view username); @@ -444,6 +445,9 @@ bool Achievements::Initialize() if (VMManager::HasValidVM() && IsLoggedInOrLoggingIn() && EmuConfig.Achievements.HardcoreMode) DisplayHardcoreDeferredMessage(); + // Set initial notification position + UpdateNotificationPosition(); + return true; } @@ -494,6 +498,63 @@ void Achievements::DestroyClient(rc_client_t** client, std::unique_ptrreset(); } +void Achievements::UpdateNotificationPosition() +{ + // Set notification position based on achievement settings + float horizontal_position, vertical_position, direction; + + // Determine horizontal alignment + switch (EmuConfig.Achievements.NotificationPosition) + { + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::BottomLeft: + horizontal_position = 0.0f; // Left + break; + + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::Center: + case OsdOverlayPos::BottomCenter: + horizontal_position = 0.5f; // Center + break; + + case OsdOverlayPos::TopRight: + case OsdOverlayPos::CenterRight: + case OsdOverlayPos::BottomRight: + default: + horizontal_position = 1.0f; // Right + break; + } + + // Determine vertical alignment and stacking direction + switch (EmuConfig.Achievements.NotificationPosition) + { + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::TopRight: + vertical_position = 0.15f; // Top area + direction = 1.0f; // Stack downward + break; + + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::Center: + case OsdOverlayPos::CenterRight: + vertical_position = 0.5f; // Center + direction = 1.0f; // Stack downward + break; + + case OsdOverlayPos::BottomLeft: + case OsdOverlayPos::BottomCenter: + case OsdOverlayPos::BottomRight: + default: + vertical_position = 0.85f; // Bottom area + direction = -1.0f; // Stack upward + break; + } + + ImGuiFullscreen::SetNotificationPosition(horizontal_position, vertical_position, direction); +} + void Achievements::UpdateSettings(const Pcsx2Config::AchievementsOptions& old_config) { if (IsUsingRAIntegration()) @@ -549,6 +610,10 @@ void Achievements::UpdateSettings(const Pcsx2Config::AchievementsOptions& old_co rc_client_set_unofficial_enabled(s_client, EmuConfig.Achievements.UnofficialTestMode); } + // Update notification position if it changed + if (EmuConfig.Achievements.NotificationPosition != old_config.NotificationPosition) + UpdateNotificationPosition(); + // in case cache directory changed EnsureCacheDirectoriesExist(); } @@ -1915,6 +1980,115 @@ static float IndicatorOpacity(const T& i) return (i.active) ? opacity : (1.0f - opacity); } +static ImVec2 CalculateOverlayPosition(const ImGuiIO& io, float padding, AchievementOverlayPosition alignment) +{ + switch (alignment) + { + case AchievementOverlayPosition::TopLeft: + return ImVec2(padding, padding); + case AchievementOverlayPosition::TopCenter: + return ImVec2(io.DisplaySize.x * 0.5f, padding); + case AchievementOverlayPosition::TopRight: + return ImVec2(io.DisplaySize.x - padding, padding); + case AchievementOverlayPosition::CenterLeft: + return ImVec2(padding, io.DisplaySize.y * 0.5f); + case AchievementOverlayPosition::Center: + return ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f); + case AchievementOverlayPosition::CenterRight: + return ImVec2(io.DisplaySize.x - padding, io.DisplaySize.y * 0.5f); + case AchievementOverlayPosition::BottomLeft: + return ImVec2(padding, io.DisplaySize.y - padding); + case AchievementOverlayPosition::BottomCenter: + return ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y - padding); + case AchievementOverlayPosition::BottomRight: + default: + return ImVec2(io.DisplaySize.x - padding, io.DisplaySize.y - padding); + } +} + +static ImVec2 AdjustPositionForAlignment(const ImVec2& base_position, const ImVec2& element_size, AchievementOverlayPosition alignment) +{ + ImVec2 adjusted = base_position; + + // Adjust for horizontal alignment + switch (alignment) + { + case AchievementOverlayPosition::TopLeft: + case AchievementOverlayPosition::CenterLeft: + case AchievementOverlayPosition::BottomLeft: + // Left aligned no adjustment needed for x + break; + + case AchievementOverlayPosition::TopCenter: + case AchievementOverlayPosition::Center: + case AchievementOverlayPosition::BottomCenter: + // Center aligned offset by half element width + adjusted.x -= element_size.x * 0.5f; + break; + + case AchievementOverlayPosition::TopRight: + case AchievementOverlayPosition::CenterRight: + case AchievementOverlayPosition::BottomRight: + default: + // Right aligned offset by full element width + adjusted.x -= element_size.x; + break; + } + + // Adjust for vertical alignment + switch (alignment) + { + case AchievementOverlayPosition::TopLeft: + case AchievementOverlayPosition::TopCenter: + case AchievementOverlayPosition::TopRight: + // Top aligned no adjustment needed for y + break; + + case AchievementOverlayPosition::CenterLeft: + case AchievementOverlayPosition::Center: + case AchievementOverlayPosition::CenterRight: + // Center aligned offset by half element height + adjusted.y -= element_size.y * 0.5f; + break; + + case AchievementOverlayPosition::BottomLeft: + case AchievementOverlayPosition::BottomCenter: + case AchievementOverlayPosition::BottomRight: + default: + // Bottom aligned offset by full element height + adjusted.y -= element_size.y; + break; + } + + return adjusted; +} + +static ImVec2 GetStackingDirection(AchievementOverlayPosition alignment) +{ + switch (alignment) + { + case AchievementOverlayPosition::TopLeft: + case AchievementOverlayPosition::TopCenter: + case AchievementOverlayPosition::TopRight: + return ImVec2(0.0f, 1.0f); // Stack downward + + case AchievementOverlayPosition::BottomLeft: + case AchievementOverlayPosition::BottomCenter: + case AchievementOverlayPosition::BottomRight: + default: + return ImVec2(0.0f, -1.0f); // Stack upward + + case AchievementOverlayPosition::CenterLeft: + return ImVec2(1.0f, 0.0f); // Stack rightward + + case AchievementOverlayPosition::CenterRight: + return ImVec2(-1.0f, 0.0f); // Stack leftward + + case AchievementOverlayPosition::Center: + return ImVec2(0.0f, -1.0f); // Stack upward for center + } +} + void Achievements::DrawGameOverlays() { @@ -1930,13 +2104,14 @@ void Achievements::DrawGameOverlays() const float padding = LayoutScale(10.0f); const ImVec2 image_size = LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT); const ImGuiIO& io = ImGui::GetIO(); - ImVec2 position = ImVec2(io.DisplaySize.x - padding, io.DisplaySize.y - padding); + ImVec2 position = CalculateOverlayPosition(io, padding, EmuConfig.Achievements.OverlayPosition); ImDrawList* dl = ImGui::GetBackgroundDrawList(); if (!s_active_challenge_indicators.empty()) { + const ImVec2 stack_direction = GetStackingDirection(EmuConfig.Achievements.OverlayPosition); + ImVec2 current_position = AdjustPositionForAlignment(position, image_size, EmuConfig.Achievements.OverlayPosition); 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_active_challenge_indicators.begin(); it != s_active_challenge_indicators.end();) { @@ -1949,7 +2124,26 @@ void Achievements::DrawGameOverlays() { dl->AddImage(reinterpret_cast(badge->GetNativeHandle()), current_position, current_position + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col); - current_position.x -= x_advance; + + // For horizontal layouts, go horizontally for vertical layouts, stay in the same row + if (std::abs(stack_direction.x) > 0.0f) + { + current_position.x += stack_direction.x * x_advance; + } + else + { + // For right-aligned vertical layouts, move left for next indicator + switch (EmuConfig.Achievements.OverlayPosition) + { + case AchievementOverlayPosition::TopRight: + case AchievementOverlayPosition::BottomRight: + current_position.x -= x_advance; + break; + default: + current_position.x += x_advance; + break; + } + } } if (!indicator.active && opacity <= 0.01f) @@ -1963,7 +2157,9 @@ void Achievements::DrawGameOverlays() } } - position.y -= image_size.y + padding; + // Go to the next row/column for anymore overlays + position.x += stack_direction.x * (image_size.x + padding); + position.y += stack_direction.y * (image_size.y + padding); } if (s_active_progress_indicator.has_value()) @@ -1976,9 +2172,12 @@ void Achievements::DrawGameOverlays() const char* text_end = text_start + std::strlen(text_start); const ImVec2 text_size = g_medium_font->CalcTextSizeA(g_medium_font->FontSize, FLT_MAX, 0.0f, text_start, text_end); - const ImVec2 box_min = - ImVec2(position.x - image_size.x - text_size.x - spacing - padding * 2.0f, position.y - image_size.y - padding * 2.0f); - const ImVec2 box_max = position; + const ImVec2 progress_box_size = ImVec2(image_size.x + text_size.x + spacing + padding * 2.0f, image_size.y + padding * 2.0f); + const ImVec2 stack_direction = GetStackingDirection(EmuConfig.Achievements.OverlayPosition); + const ImVec2 box_position = AdjustPositionForAlignment(position, progress_box_size, EmuConfig.Achievements.OverlayPosition); + + const ImVec2 box_min = box_position; + const ImVec2 box_max = box_position + progress_box_size; const float box_rounding = LayoutScale(1.0f); dl->AddRectFilled(box_min, box_max, ImGui::GetColorU32(ImVec4(0.13f, 0.13f, 0.13f, opacity * 0.5f)), box_rounding); @@ -2002,11 +2201,15 @@ void Achievements::DrawGameOverlays() s_active_progress_indicator.reset(); } - position.y -= image_size.y + padding * 3.0f; + // Go to the next row/column for anymore overlays + position.x += stack_direction.x * (progress_box_size.x + padding); + position.y += stack_direction.y * (progress_box_size.y + padding); } if (!s_active_leaderboard_trackers.empty()) { + const ImVec2 stack_direction = GetStackingDirection(EmuConfig.Achievements.OverlayPosition); + for (auto it = s_active_leaderboard_trackers.begin(); it != s_active_leaderboard_trackers.end();) { const LeaderboardTrackerIndicator& indicator = *it; @@ -2019,8 +2222,11 @@ void Achievements::DrawGameOverlays() const ImVec2 size = ImGuiFullscreen::g_medium_font->CalcTextSizeA( ImGuiFullscreen::g_medium_font->FontSize, FLT_MAX, 0.0f, width_string.c_str(), width_string.end_ptr()); - const ImVec2 box_min = ImVec2(position.x - size.x - padding * 2.0f, position.y - size.y - padding * 2.0f); - const ImVec2 box_max = position; + const ImVec2 tracker_box_size = ImVec2(size.x + padding * 2.0f, size.y + padding * 2.0f); + const ImVec2 box_position = AdjustPositionForAlignment(position, tracker_box_size, EmuConfig.Achievements.OverlayPosition); + + const ImVec2 box_min = box_position; + const ImVec2 box_max = box_position + tracker_box_size; const float box_rounding = LayoutScale(1.0f); dl->AddRectFilled(box_min, box_max, ImGui::GetColorU32(ImVec4(0.13f, 0.13f, 0.13f, opacity * 0.5f)), box_rounding); dl->AddRect(box_min, box_max, ImGui::GetColorU32(ImVec4(0.8f, 0.8f, 0.8f, opacity)), box_rounding); @@ -2048,7 +2254,9 @@ void Achievements::DrawGameOverlays() ++it; } - position.x = box_min.x - padding; + // Go to the next position for anymore trackers + position.x += stack_direction.x * (tracker_box_size.x + padding); + position.y += stack_direction.y * (tracker_box_size.y + padding); } // Uncomment if there are any other overlays above this one. diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 0b23a2f42d..ab06a06ee0 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -341,7 +341,14 @@ enum class OsdOverlayPos : u8 { None, TopLeft, + TopCenter, TopRight, + CenterLeft, + Center, + CenterRight, + BottomLeft, + BottomCenter, + BottomRight, }; enum class TexturePreloadingLevel : u8 @@ -452,6 +459,20 @@ enum class GSNativeScaling : u8 MaxCount }; +enum class AchievementOverlayPosition : u8 +{ + TopLeft, + TopCenter, + TopRight, + CenterLeft, + Center, + CenterRight, + BottomLeft, + BottomCenter, + BottomRight, + MaxCount +}; + // -------------------------------------------------------------------------------------- // TraceLogsEE // -------------------------------------------------------------------------------------- @@ -1213,6 +1234,8 @@ struct Pcsx2Config static constexpr const char* DEFAULT_UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav"; static constexpr const char* DEFAULT_LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav"; + static const char* OverlayPositionNames[(size_t)AchievementOverlayPosition::MaxCount + 1]; + BITFIELD32() bool Enabled : 1, @@ -1231,6 +1254,8 @@ struct Pcsx2Config u32 NotificationsDuration = DEFAULT_NOTIFICATION_DURATION; u32 LeaderboardsDuration = DEFAULT_LEADERBOARD_DURATION; + AchievementOverlayPosition OverlayPosition = AchievementOverlayPosition::BottomRight; + OsdOverlayPos NotificationPosition = OsdOverlayPos::TopLeft; std::string InfoSoundName; std::string UnlockSoundName; diff --git a/pcsx2/GSDumpReplayer.cpp b/pcsx2/GSDumpReplayer.cpp index 6d8315798e..b3bfe5774c 100644 --- a/pcsx2/GSDumpReplayer.cpp +++ b/pcsx2/GSDumpReplayer.cpp @@ -9,6 +9,7 @@ #include "Gif_Unit.h" #include "Host.h" #include "ImGui/ImGuiManager.h" +#include "ImGui/ImGuiOverlays.h" #include "R3000A.h" #include "R5900.h" #include "VMManager.h" @@ -379,8 +380,9 @@ void GSDumpReplayer::RenderUI() do \ { \ text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), -1.0f, (text), nullptr, nullptr); \ - dl->AddText(font, font->FontSize, ImVec2(GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? ImGuiManager::GetWindowWidth() - margin - text_size.x + shadow_offset : margin + shadow_offset, position_y + shadow_offset), IM_COL32(0, 0, 0, 100), (text)); \ - dl->AddText(font, font->FontSize, ImVec2(GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? ImGuiManager::GetWindowWidth() - margin - text_size.x : margin, position_y), color, (text)); \ + const ImVec2 text_pos = CalculatePerformanceOverlayTextPosition(GSConfig.OsdPerformancePos, margin, text_size, ImGuiManager::GetWindowWidth(), position_y); \ + dl->AddText(font, font->FontSize, ImVec2(text_pos.x + shadow_offset, text_pos.y + shadow_offset), IM_COL32(0, 0, 0, 100), (text)); \ + dl->AddText(font, font->FontSize, text_pos, color, (text)); \ position_y += text_size.y + spacing; \ } while (0) diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index b289af3392..bbc2785a62 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -3653,6 +3653,30 @@ void FullscreenUI::DrawInterfaceSettingsPage() DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TRIANGLE_EXCLAMATION, "Warn About Unsafe Settings"), FSUI_CSTR("Displays warnings when settings are enabled which may break games."), "EmuCore", "WarnAboutUnsafeSettings", true); + // OSD Positioning Options + static constexpr const char* s_osd_position_options[] = { + FSUI_NSTR("None"), + FSUI_NSTR("Top Left"), + FSUI_NSTR("Top Center"), + FSUI_NSTR("Top Right"), + FSUI_NSTR("Center Left"), + FSUI_NSTR("Center"), + FSUI_NSTR("Center Right"), + FSUI_NSTR("Bottom Left"), + FSUI_NSTR("Bottom Center"), + FSUI_NSTR("Bottom Right"), + }; + static constexpr const char* s_osd_position_values[] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + }; + + DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_COMMENT, "OSD Messages Position"), + FSUI_CSTR("Determines where on-screen display messages are positioned."), "EmuCore/GS", "OsdMessagesPos", "1", + s_osd_position_options, s_osd_position_values, std::size(s_osd_position_options), true); + DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_CHART_BAR, "OSD Performance Position"), + FSUI_CSTR("Determines where performance statistics are positioned."), "EmuCore/GS", "OsdPerformancePos", "3", + s_osd_position_options, s_osd_position_values, std::size(s_osd_position_options), true); + MenuHeading(FSUI_CSTR("Operations")); if (MenuButton(FSUI_ICONSTR(ICON_FA_DUMPSTER_FIRE, "Reset Settings"), FSUI_CSTR("Resets configuration to defaults (excluding controller settings)."), !IsEditingGameSettings(bsi))) @@ -7598,6 +7622,34 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock& se DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_HEARTBEAT_ALT, "Enable In-Game Overlays"), FSUI_CSTR("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."), "Achievements", "Overlays", true, enabled); + + if (enabled) + { + const char* alignment_options[] = { + TRANSLATE_NOOP("FullscreenUI", "Top Left"), + TRANSLATE_NOOP("FullscreenUI", "Top Center"), + TRANSLATE_NOOP("FullscreenUI", "Top Right"), + TRANSLATE_NOOP("FullscreenUI", "Center Left"), + TRANSLATE_NOOP("FullscreenUI", "Center"), + TRANSLATE_NOOP("FullscreenUI", "Center Right"), + TRANSLATE_NOOP("FullscreenUI", "Bottom Left"), + TRANSLATE_NOOP("FullscreenUI", "Bottom Center"), + TRANSLATE_NOOP("FullscreenUI", "Bottom Right") + }; + + DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_ALIGN_CENTER, "Overlay Position"), + FSUI_CSTR("Determines where achievement overlays are positioned on the screen."), "Achievements", "OverlayPosition", + 8, alignment_options, std::size(alignment_options), true, 0, enabled); + + const bool notifications_enabled = Host::GetBaseBoolSettingValue("Achievements", "Notifications", true) || + Host::GetBaseBoolSettingValue("Achievements", "LeaderboardNotifications", true); + if (notifications_enabled) + { + DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_BELL, "Notification Position"), + FSUI_CSTR("Determines where achievement notification popups are positioned on the screen."), "Achievements", "NotificationPosition", + 2, alignment_options, std::size(alignment_options), true, 0, enabled); + } + } DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LOCK, "Encore Mode"), FSUI_CSTR("When enabled, each session will behave as if no achievements have been unlocked."), "Achievements", "EncoreMode", false, enabled); diff --git a/pcsx2/ImGui/ImGuiFullscreen.cpp b/pcsx2/ImGui/ImGuiFullscreen.cpp index f896bdd8c5..320bc6020b 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.cpp +++ b/pcsx2/ImGui/ImGuiFullscreen.cpp @@ -181,6 +181,9 @@ namespace ImGuiFullscreen }; static std::vector s_notifications; + static float s_notification_vertical_position = 0.15f; + static float s_notification_vertical_direction = 1.0f; + static float s_notification_horizontal_position = 0.0f; // 0.0 = left, 0.5 = center, 1.0 = right static std::string s_toast_title; static std::string s_toast_message; @@ -697,7 +700,17 @@ void ImGuiFullscreen::EndLayout() const float notification_margin = LayoutScale(10.0f); const float spacing = LayoutScale(10.0f); const float notification_vertical_pos = GetNotificationVerticalPosition(); - ImVec2 position(notification_margin, notification_vertical_pos * ImGui::GetIO().DisplaySize.y + + + // Get the horizonal position based on alignment + float horizontal_pos; + if (s_notification_horizontal_position <= 0.0f) + horizontal_pos = notification_margin; // Left + else if (s_notification_horizontal_position >= 1.0f) + horizontal_pos = ImGui::GetIO().DisplaySize.x - notification_margin; // Right + else + horizontal_pos = ImGui::GetIO().DisplaySize.x * s_notification_horizontal_position; // Center + + ImVec2 position(horizontal_pos, notification_vertical_pos * ImGui::GetIO().DisplaySize.y + ((notification_vertical_pos >= 0.5f) ? -notification_margin : notification_margin)); DrawBackgroundProgressDialogs(position, spacing); DrawNotifications(position, spacing); @@ -2672,9 +2685,6 @@ void ImGuiFullscreen::DrawMessageDialog() } } -static float s_notification_vertical_position = 0.15f; -static float s_notification_vertical_direction = 1.0f; - float ImGuiFullscreen::GetNotificationVerticalPosition() { return s_notification_vertical_position; @@ -2691,6 +2701,13 @@ void ImGuiFullscreen::SetNotificationVerticalPosition(float position, float dire s_notification_vertical_direction = direction; } +void ImGuiFullscreen::SetNotificationPosition(float horizontal_position, float vertical_position, float direction) +{ + s_notification_horizontal_position = horizontal_position; + s_notification_vertical_position = vertical_position; + s_notification_vertical_direction = direction; +} + ImGuiID ImGuiFullscreen::GetBackgroundProgressID(const char* str_id) { return ImHashStr(str_id); @@ -2952,7 +2969,14 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) } } - const ImVec2 box_min(position.x, actual_y); + // Adjust horizontal position based on alignment + float final_x = position.x; + if (s_notification_horizontal_position >= 1.0f) + final_x = position.x - box_width; + else if (s_notification_horizontal_position > 0.0f && s_notification_horizontal_position < 1.0f) + final_x = position.x - (box_width * 0.5f); + + const ImVec2 box_min(final_x, actual_y); const ImVec2 box_max(box_min.x + box_width, box_min.y + box_height); const u32 background_color = (toast_background_color & ~IM_COL32_A_MASK) | (opacity << IM_COL32_A_SHIFT); const u32 border_color = (toast_border_color & ~IM_COL32_A_MASK) | (opacity << IM_COL32_A_SHIFT); diff --git a/pcsx2/ImGui/ImGuiFullscreen.h b/pcsx2/ImGui/ImGuiFullscreen.h index 01f0cb2c8a..e57a3be9d0 100644 --- a/pcsx2/ImGui/ImGuiFullscreen.h +++ b/pcsx2/ImGui/ImGuiFullscreen.h @@ -273,6 +273,7 @@ namespace ImGuiFullscreen float GetNotificationVerticalPosition(); float GetNotificationVerticalDirection(); void SetNotificationVerticalPosition(float position, float direction); + void SetNotificationPosition(float horizontal_position, float vertical_position, float direction); void OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value); void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value); diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index 2c361e200a..352d946d5c 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -730,8 +730,34 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) const float padding = std::ceil(8.0f * scale); const float rounding = std::ceil(5.0f * scale); const float max_width = s_window_width - (margin + padding) * 2.0f; - float position_x = GSConfig.OsdMessagesPos == OsdOverlayPos::TopRight ? GetWindowWidth() - margin : margin; + float position_y = margin; + switch (GSConfig.OsdMessagesPos) + { + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::TopRight: + position_y = margin; + break; + + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::Center: + case OsdOverlayPos::CenterRight: + position_y = s_window_height * 0.5f; + break; + + case OsdOverlayPos::BottomLeft: + case OsdOverlayPos::BottomCenter: + case OsdOverlayPos::BottomRight: + // For bottom positions, start from the bottom and let messages stack upward + position_y = s_window_height - margin; + break; + + case OsdOverlayPos::None: + default: + position_y = margin; + break; + } auto iter = s_osd_active_messages.begin(); while (iter != s_osd_active_messages.end()) @@ -798,7 +824,18 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) const ImVec2 text_size( font->CalcTextSizeA(font->FontSize, max_width, max_width, msg.text.c_str(), msg.text.c_str() + msg.text.length())); const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f); - const ImVec2 pos(position_x - (GSConfig.OsdMessagesPos == OsdOverlayPos::TopRight ? size.x : 0), actual_y); + + // For bottom positions, adjust actual_y to try to account for message height + float final_y = actual_y; + if (GSConfig.OsdMessagesPos == OsdOverlayPos::BottomLeft || + GSConfig.OsdMessagesPos == OsdOverlayPos::BottomCenter || + GSConfig.OsdMessagesPos == OsdOverlayPos::BottomRight) + { + final_y = actual_y - size.y; + } + + const ImVec2 base_pos = CalculateOSDPosition(GSConfig.OsdMessagesPos, margin, size, s_window_width, s_window_height); + const ImVec2 pos(base_pos.x, final_y); const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding); ImDrawList* dl = ImGui::GetBackgroundDrawList(); @@ -806,7 +843,18 @@ void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time) dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x48, 0x48, 0x48, opacity), rounding); dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, opacity), msg.text.c_str(), msg.text.c_str() + msg.text.length(), max_width, &text_rect); - position_y += size.y + spacing; + + // Stack direction depends on the position upward for bottom positions, downward for others + if (GSConfig.OsdMessagesPos == OsdOverlayPos::BottomLeft || + GSConfig.OsdMessagesPos == OsdOverlayPos::BottomCenter || + GSConfig.OsdMessagesPos == OsdOverlayPos::BottomRight) + { + position_y -= (size.y + spacing); // Stack upward for bottom positions + } + else + { + position_y += size.y + spacing; // Stack downward for top/center positions + } } } diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index fee102604c..0e0fdaee07 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -49,6 +49,71 @@ InputRecordingUI::InputRecordingData g_InputRecordingData; +// OSD positioning funcs +ImVec2 CalculateOSDPosition(OsdOverlayPos position, float margin, const ImVec2& text_size, float window_width, float window_height) +{ + switch (position) + { + case OsdOverlayPos::TopLeft: + return ImVec2(margin, margin); + case OsdOverlayPos::TopCenter: + return ImVec2((window_width - text_size.x) * 0.5f, margin); + case OsdOverlayPos::TopRight: + return ImVec2(window_width - margin - text_size.x, margin); + case OsdOverlayPos::CenterLeft: + return ImVec2(margin, (window_height - text_size.y) * 0.5f); + case OsdOverlayPos::Center: + return ImVec2((window_width - text_size.x) * 0.5f, (window_height - text_size.y) * 0.5f); + case OsdOverlayPos::CenterRight: + return ImVec2(window_width - margin - text_size.x, (window_height - text_size.y) * 0.5f); + case OsdOverlayPos::BottomLeft: + return ImVec2(margin, window_height - margin - text_size.y); + case OsdOverlayPos::BottomCenter: + return ImVec2((window_width - text_size.x) * 0.5f, window_height - margin - text_size.y); + case OsdOverlayPos::BottomRight: + return ImVec2(window_width - margin - text_size.x, window_height - margin - text_size.y); + case OsdOverlayPos::None: + default: + return ImVec2(0.0f, 0.0f); + } +} + +ImVec2 CalculatePerformanceOverlayTextPosition(OsdOverlayPos position, float margin, const ImVec2& text_size, float window_width, float position_y) +{ + const float abs_margin = std::abs(margin); + + // Get the X position based on horizontal alignment + float x_pos; + switch (position) + { + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::BottomLeft: + x_pos = abs_margin; // Left alignment + break; + + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::Center: + case OsdOverlayPos::BottomCenter: + x_pos = (window_width - text_size.x) * 0.5f; // Center alignment + break; + + case OsdOverlayPos::TopRight: + case OsdOverlayPos::CenterRight: + case OsdOverlayPos::BottomRight: + default: + x_pos = window_width - text_size.x - abs_margin; // Right alignment + break; + } + + return ImVec2(x_pos, position_y); +} + +bool ShouldUseLeftAlignment(OsdOverlayPos position) +{ + return (position == OsdOverlayPos::TopLeft || position == OsdOverlayPos::CenterLeft || position == OsdOverlayPos::BottomLeft); +} + namespace ImGuiManager { static void FormatProcessorStat(SmallStringBase& text, double usage, double time); @@ -107,17 +172,38 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f SmallString text; ImVec2 text_size; - if (GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft) - margin = -margin; + // Adjust initial Y position based on vertical alignment + switch (GSConfig.OsdPerformancePos) + { + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::Center: + case OsdOverlayPos::CenterRight: + + position_y = (GetWindowHeight() - (fixed_font->FontSize * 8.0f)) * 0.5f; + break; + + case OsdOverlayPos::BottomLeft: + case OsdOverlayPos::BottomCenter: + case OsdOverlayPos::BottomRight: + + position_y = GetWindowHeight() - margin - (fixed_font->FontSize * 15.0f + spacing * 14.0f); + break; + + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::TopRight: + default: + // Top alignment keeps the passed position_y + break; + } #define DRAW_LINE(font, text, color) \ do \ { \ text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), -1.0f, (text), nullptr, nullptr); \ - dl->AddText(font, font->FontSize, \ - ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 0 : GetWindowWidth() - text_size.x) - margin + shadow_offset, position_y + shadow_offset), \ - IM_COL32(0, 0, 0, 100), (text)); \ - dl->AddText(font, font->FontSize, ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 0 : GetWindowWidth() - text_size.x) - margin, position_y), color, (text)); \ + const ImVec2 text_pos = CalculatePerformanceOverlayTextPosition(GSConfig.OsdPerformancePos, margin, text_size, GetWindowWidth(), position_y); \ + dl->AddText(font, font->FontSize, ImVec2(text_pos.x + shadow_offset, text_pos.y + shadow_offset), IM_COL32(0, 0, 0, 100), (text)); \ + dl->AddText(font, font->FontSize, text_pos, color, (text)); \ position_y += text_size.y + spacing; \ } while (0) @@ -299,7 +385,9 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f { const ImVec2 history_size(200.0f * scale, 50.0f * scale); ImGui::SetNextWindowSize(ImVec2(history_size.x, history_size.y)); - ImGui::SetNextWindowPos(ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 0 : GetWindowWidth() - history_size.x) - margin, position_y)); + + const ImVec2 window_pos = CalculatePerformanceOverlayTextPosition(GSConfig.OsdPerformancePos, margin, history_size, GetWindowWidth(), position_y); + ImGui::SetNextWindowPos(window_pos); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.25f)); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); @@ -336,20 +424,59 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f text.clear(); text.append_format("Max: {:.1f} ms", max); text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.length()); - win_dl->AddText(ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 2.0f * spacing : wpos.x + history_size.x - text_size.x - spacing) + shadow_offset, - wpos.y + shadow_offset), + + float text_x; + switch (GSConfig.OsdPerformancePos) + { + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::BottomLeft: + text_x = wpos.x + 2.0f * spacing; // Left alignment within window + break; + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::Center: + case OsdOverlayPos::BottomCenter: + text_x = wpos.x + (history_size.x - text_size.x) * 0.5f; // Center alignment within window + break; + case OsdOverlayPos::TopRight: + case OsdOverlayPos::CenterRight: + case OsdOverlayPos::BottomRight: + default: + text_x = wpos.x + history_size.x - text_size.x - spacing; // Right alignment within window + break; + } + win_dl->AddText(ImVec2(text_x + shadow_offset, wpos.y + shadow_offset), IM_COL32(0, 0, 0, 100), text.c_str(), text.c_str() + text.length()); - win_dl->AddText(ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 2.0f * spacing : wpos.x + history_size.x - text_size.x - spacing), wpos.y), + win_dl->AddText(ImVec2(text_x, wpos.y), IM_COL32(255, 255, 255, 255), text.c_str(), text.c_str() + text.length()); text.clear(); text.append_format("Min: {:.1f} ms", min); text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.length()); - win_dl->AddText(ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 2.0f * spacing : wpos.x + history_size.x - text_size.x - spacing) + shadow_offset, - wpos.y + history_size.y - fixed_font->FontSize + shadow_offset), + + float min_text_x; + switch (GSConfig.OsdPerformancePos) + { + case OsdOverlayPos::TopLeft: + case OsdOverlayPos::CenterLeft: + case OsdOverlayPos::BottomLeft: + min_text_x = wpos.x + 2.0f * spacing; // Left alignment within window + break; + case OsdOverlayPos::TopCenter: + case OsdOverlayPos::Center: + case OsdOverlayPos::BottomCenter: + min_text_x = wpos.x + (history_size.x - text_size.x) * 0.5f; // Center alignment within window + break; + case OsdOverlayPos::TopRight: + case OsdOverlayPos::CenterRight: + case OsdOverlayPos::BottomRight: + default: + min_text_x = wpos.x + history_size.x - text_size.x - spacing; // Right alignment within window + break; + } + win_dl->AddText(ImVec2(min_text_x + shadow_offset, wpos.y + history_size.y - fixed_font->FontSize + shadow_offset), IM_COL32(0, 0, 0, 100), text.c_str(), text.c_str() + text.length()); - win_dl->AddText(ImVec2((GSConfig.OsdPerformancePos == OsdOverlayPos::TopLeft ? 2.0f * spacing : wpos.x + history_size.x - text_size.x - spacing), - wpos.y + history_size.y - fixed_font->FontSize), + win_dl->AddText(ImVec2(min_text_x, wpos.y + history_size.y - fixed_font->FontSize), IM_COL32(255, 255, 255, 255), text.c_str(), text.c_str() + text.length()); } ImGui::End(); @@ -362,7 +489,12 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f { if (GSConfig.OsdShowIndicators) { - DRAW_LINE(standard_font, ICON_FA_PAUSE, IM_COL32(255, 255, 255, 255)); + // We should put the Pause icon in the top right regardless of performance overlay position + text = ICON_FA_PAUSE; + text_size = standard_font->CalcTextSizeA(standard_font->FontSize, std::numeric_limits::max(), -1.0f, text.c_str(), nullptr, nullptr); + const ImVec2 pause_pos(GetWindowWidth() - margin - text_size.x, margin); + dl->AddText(standard_font, standard_font->FontSize, ImVec2(pause_pos.x + shadow_offset, pause_pos.y + shadow_offset), IM_COL32(0, 0, 0, 100), text.c_str()); + dl->AddText(standard_font, standard_font->FontSize, pause_pos, IM_COL32(255, 255, 255, 255), text.c_str()); } } @@ -1147,7 +1279,7 @@ void ImGuiManager::RenderOverlays() { const float scale = ImGuiManager::GetGlobalScale(); const float margin = std::ceil(10.0f * scale); - const float spacing = std::ceil(5.0f * scale); + const float spacing = std::ceil(5.0f * scale); float position_y = margin; DrawVideoCaptureOverlay(position_y, scale, margin, spacing); diff --git a/pcsx2/ImGui/ImGuiOverlays.h b/pcsx2/ImGui/ImGuiOverlays.h index 158fe54277..090bca7e5e 100644 --- a/pcsx2/ImGui/ImGuiOverlays.h +++ b/pcsx2/ImGui/ImGuiOverlays.h @@ -4,12 +4,19 @@ #pragma once #include "ImGuiManager.h" +#include "Config.h" + +struct ImVec2; namespace ImGuiManager { void RenderOverlays(); } +ImVec2 CalculateOSDPosition(OsdOverlayPos position, float margin, const ImVec2& text_size, float window_width, float window_height); +ImVec2 CalculatePerformanceOverlayTextPosition(OsdOverlayPos position, float margin, const ImVec2& text_size, float window_width, float position_y); +bool ShouldUseLeftAlignment(OsdOverlayPos position); + namespace SaveStateSelectorUI { static constexpr float DEFAULT_OPEN_TIME = 5.0f; diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 466c5bffdf..d311515843 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -671,6 +671,18 @@ const char* Pcsx2Config::GSOptions::CaptureContainers[] = { nullptr}; const char* Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER = "mp4"; +const char* Pcsx2Config::AchievementsOptions::OverlayPositionNames[(size_t)AchievementOverlayPosition::MaxCount + 1] = { + "TopLeft", + "TopCenter", + "TopRight", + "CenterLeft", + "Center", + "CenterRight", + "BottomLeft", + "BottomCenter", + "BottomRight", + nullptr}; + const char* Pcsx2Config::GSOptions::GetRendererName(GSRendererType type) { switch (type) @@ -1863,6 +1875,8 @@ void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(Overlays); SettingsWrapEntry(NotificationsDuration); SettingsWrapEntry(LeaderboardsDuration); + SettingsWrapIntEnumEx(OverlayPosition, "OverlayPosition"); + SettingsWrapIntEnumEx(NotificationPosition, "NotificationPosition"); SettingsWrapEntry(InfoSoundName); SettingsWrapEntry(UnlockSoundName); SettingsWrapEntry(LBSubmitSoundName); @@ -1877,7 +1891,8 @@ void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap) bool Pcsx2Config::AchievementsOptions::operator==(const AchievementsOptions& right) const { - return OpEqu(bitset) && OpEqu(NotificationsDuration) && OpEqu(LeaderboardsDuration); + return OpEqu(bitset) && OpEqu(NotificationsDuration) && OpEqu(LeaderboardsDuration) && + OpEqu(OverlayPosition) && OpEqu(NotificationPosition); } bool Pcsx2Config::AchievementsOptions::operator!=(const AchievementsOptions& right) const