ImGuiManager: Allow repositioning OSD messages

This commit is contained in:
Stenzek
2026-01-08 20:05:19 +10:00
parent 4e0c42100a
commit bb8179f617
10 changed files with 115 additions and 73 deletions

View File

@@ -2287,6 +2287,12 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_VSTR("Determines the margin between the edge of the screen and on-screen messages."),
"Display", "OSDMargin", ImGuiManager::DEFAULT_SCREEN_MARGIN, 0.0f, 100.0f, 1.0f, 1.0f,
"%.0fpx");
DrawEnumSetting(bsi, FSUI_ICONVSTR(ICON_FA_LOCATION_DOT, "Message Location"),
FSUI_VSTR("Selects which location on the screen messages are displayed."), "Display",
"OSDMessageLocation", Settings::DEFAULT_OSD_MESSAGE_LOCATION, &Settings::ParseNotificationLocation,
&Settings::GetNotificationLocationName, &Settings::GetNotificationLocationDisplayName,
NotificationLocation::MaxCount);
DrawToggleSetting(bsi, FSUI_ICONVSTR(ICON_FA_CIRCLE_EXCLAMATION, "Show Messages"),
FSUI_VSTR("Shows on-screen-display messages when events occur. Errors and warnings are still "
"displayed regardless of this setting."),

View File

@@ -484,6 +484,7 @@ TRANSLATE_NOOP("FullscreenUI", "Menu Background");
TRANSLATE_NOOP("FullscreenUI", "Menu Borders");
TRANSLATE_NOOP("FullscreenUI", "Merge Multi-Disc Games");
TRANSLATE_NOOP("FullscreenUI", "Merges multi-disc games into one item in the game list.");
TRANSLATE_NOOP("FullscreenUI", "Message Location");
TRANSLATE_NOOP("FullscreenUI", "Minimal Output Latency");
TRANSLATE_NOOP("FullscreenUI", "Move Down");
TRANSLATE_NOOP("FullscreenUI", "Move Up");
@@ -671,6 +672,7 @@ TRANSLATE_NOOP("FullscreenUI", "Selects the screen location for achievement and
TRANSLATE_NOOP("FullscreenUI", "Selects the screen location for challenge/progress indicators, and leaderboard trackers.");
TRANSLATE_NOOP("FullscreenUI", "Selects the type of emulated controller for this port.");
TRANSLATE_NOOP("FullscreenUI", "Selects the view that the game list will open to.");
TRANSLATE_NOOP("FullscreenUI", "Selects which location on the screen messages are displayed.");
TRANSLATE_NOOP("FullscreenUI", "Serial");
TRANSLATE_NOOP("FullscreenUI", "Session: {}");
TRANSLATE_NOOP("FullscreenUI", "Set Cover Image");

View File

@@ -4978,16 +4978,25 @@ FullscreenUI::NotificationLayout::NotificationLayout(NotificationLocation locati
static constexpr float top_start_pct = 0.15f;
#endif
const float top_margin = ImFloor(top_start_pct * ImGui::GetIO().DisplaySize.y);
CalcStartPosition(screen_margin, top_margin);
}
FullscreenUI::NotificationLayout::NotificationLayout(NotificationLocation location, float spacing, float screen_margin)
: m_spacing(spacing), m_location(location)
{
CalcStartPosition(screen_margin, screen_margin);
}
void FullscreenUI::NotificationLayout::CalcStartPosition(float screen_margin, float top_margin)
{
const ImGuiIO& io = ImGui::GetIO();
switch (m_location)
{
case NotificationLocation::TopLeft:
{
m_current_position.x = screen_margin;
// need to consider osd message size
m_current_position.y = std::max(std::max(screen_margin, top_start_pct * io.DisplaySize.y),
ImGuiManager::GetOSDMessageEndPosition() + m_spacing);
m_current_position.y = std::max(screen_margin, top_margin);
}
break;
@@ -5001,7 +5010,7 @@ FullscreenUI::NotificationLayout::NotificationLayout(NotificationLocation locati
case NotificationLocation::TopRight:
{
m_current_position.x = io.DisplaySize.x - screen_margin;
m_current_position.y = std::max(screen_margin, top_start_pct * io.DisplaySize.y);
m_current_position.y = std::max(screen_margin, top_margin);
}
break;
@@ -5028,6 +5037,16 @@ FullscreenUI::NotificationLayout::NotificationLayout(NotificationLocation locati
DefaultCaseIsUnreachable();
}
// don't draw over osd messages
if (m_location == g_gpu_settings.display_osd_message_location)
{
const float osd_end = ImGuiManager::GetOSDMessageEndPosition();
if (m_location >= NotificationLocation::BottomLeft)
m_current_position.y = std::min(m_current_position.y, osd_end);
else
m_current_position.y = std::max(m_current_position.y, osd_end);
}
}
bool FullscreenUI::NotificationLayout::IsVerticalAnimation() const

View File

@@ -519,8 +519,10 @@ class NotificationLayout
{
public:
NotificationLayout(NotificationLocation location);
NotificationLayout(NotificationLocation location, float spacing, float screen_margin);
ALWAYS_INLINE NotificationLocation GetLocation() const { return m_location; }
ALWAYS_INLINE const ImVec2& GetCurrentPosition() const { return m_current_position; }
bool IsVerticalAnimation() const;
ImVec2 GetFixedPosition(float width, float height);
@@ -533,6 +535,8 @@ public:
float in_duration, float out_duration, float width_coeff);
private:
void CalcStartPosition(float screen_margin, float top_margin);
ImVec2 m_current_position;
float m_spacing;
NotificationLocation m_location;

View File

@@ -400,6 +400,8 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS[i]);
}
display_osd_message_duration[static_cast<size_t>(OSDMessageType::Persistent)] = std::numeric_limits<float>::max();
display_osd_message_location = ParseNotificationLocation(si.GetStringValue("Display", "OSDMessageLocation").c_str())
.value_or(DEFAULT_OSD_MESSAGE_LOCATION);
save_state_compression = ParseSaveStateCompressionModeName(
si.GetStringValue("Main", "SaveStateCompression",
@@ -755,6 +757,8 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
TinyString::from_format("OSD{}Duration", GetDisplayOSDMessageTypeName(static_cast<OSDMessageType>(i))),
display_osd_message_duration[i]);
}
si.SetStringValue("Display", "OSDMessageLocation", GetNotificationLocationName(display_osd_message_location));
}
si.SetBoolValue("Display", "AutoResizeWindow", display_auto_resize_window);

View File

@@ -128,6 +128,7 @@ struct GPUSettings
float display_osd_margin = 0.0f;
std::array<float, 5> display_osd_message_duration = DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS;
NotificationLocation display_osd_message_location = DEFAULT_OSD_MESSAGE_LOCATION;
// texture replacements
struct TextureReplacementSettings
@@ -234,6 +235,7 @@ struct GPUSettings
static constexpr u8 DEFAULT_DISPLAY_SCREENSHOT_QUALITY = 85;
static constexpr float DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER = 2.0f;
static constexpr float DEFAULT_OSD_SCALE = 100.0f;
static constexpr NotificationLocation DEFAULT_OSD_MESSAGE_LOCATION = NotificationLocation::TopLeft;
static const std::array<float, 5> DEFAULT_DISPLAY_OSD_MESSAGE_DURATIONS;

View File

@@ -4629,6 +4629,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.display_osd_scale != old_settings.display_osd_scale ||
g_settings.display_osd_margin != old_settings.display_osd_margin ||
g_settings.display_osd_message_duration != old_settings.display_osd_message_duration ||
g_settings.display_osd_message_location != old_settings.display_osd_message_location ||
g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
g_settings.gpu_pgxp_texture_correction != old_settings.gpu_pgxp_texture_correction ||
g_settings.gpu_pgxp_color_correction != old_settings.gpu_pgxp_color_correction ||

View File

@@ -235,6 +235,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.osdMargin, "Display", "OSDMargin",
ImGuiManager::DEFAULT_SCREEN_MARGIN);
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.fullscreenUITheme, "UI", "FullscreenUITheme");
SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.osdMessageLocation, "Display", "OSDMessageLocation", &Settings::ParseNotificationLocation,
&Settings::GetNotificationLocationName, &Settings::GetNotificationLocationDisplayName,
Settings::DEFAULT_OSD_MESSAGE_LOCATION, NotificationLocation::MaxCount);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showMessages, "Display", "ShowOSDMessages", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFPS, "Display", "ShowFPS", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSpeed, "Display", "ShowSpeed", false);
@@ -582,10 +586,15 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
// OSD Tab
dialog->registerWidgetHelp(
m_ui.osdScale, tr("OSD Scale"), tr("100%"),
m_ui.osdScale, tr("Display Scale"), tr("100%"),
tr("Changes the size at which on-screen elements, including status and messages are displayed."));
dialog->registerWidgetHelp(m_ui.fullscreenUITheme, tr("Theme"), tr("Automatic"),
tr("Determines the theme to use for on-screen display elements and the Big Picture UI."));
dialog->registerWidgetHelp(m_ui.osdMargin, tr("Display Margins"), tr("0px"),
tr("Determines the margin between the edge of the screen and on-screen messages."));
dialog->registerWidgetHelp(
m_ui.osdMessageLocation, tr("Message Location"), tr("Selects which location on the screen messages are displayed."),
QString::fromStdString(Settings::GetNotificationLocationDisplayName(Settings::DEFAULT_OSD_MESSAGE_LOCATION)));
dialog->registerWidgetHelp(
m_ui.showMessages, tr("Show Messages"), tr("Checked"),
tr("Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being "

View File

@@ -293,7 +293,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<width>0</width>
<height>0</height>
</size>
</property>
@@ -763,7 +763,7 @@
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout_14" columnstretch="0,1" columnminimumwidth="100,0">
<layout class="QGridLayout" name="gridLayout_14" columnstretch="0,1" columnminimumwidth="120,0">
<property name="topMargin">
<number>6</number>
</property>
@@ -814,7 +814,17 @@
<item row="2" column="1">
<widget class="QComboBox" name="fullscreenUITheme"/>
</item>
<item row="3" column="0" colspan="2">
<item row="3" column="1">
<widget class="QComboBox" name="osdMessageLocation"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Message Location:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="showMessages">
@@ -910,7 +920,7 @@
<property name="title">
<string>Message Durations</string>
</property>
<layout class="QGridLayout" name="gridLayout_21" columnstretch="0,1,0,1" columnminimumwidth="100,0,100,0">
<layout class="QGridLayout" name="gridLayout_21" columnstretch="0,1,0,1" columnminimumwidth="120,0,120,0">
<item row="0" column="0">
<widget class="QLabel" name="osdErrorDurationLabel">
<property name="text">
@@ -1025,7 +1035,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<width>0</width>
<height>0</height>
</size>
</property>

View File

@@ -978,11 +978,11 @@ void ImGuiManager::DrawOSDMessages(Timer::Value current_time)
static constexpr float MOVE_DURATION = 0.5f;
s_state.osd_messages_end_y =
(g_gpu_settings.display_osd_message_location >= NotificationLocation::BottomLeft) ? s_state.window_height : 0.0f;
if (s_state.osd_active_messages.empty())
{
s_state.osd_messages_end_y = 0.0f;
return;
}
ImFont* const font = s_state.text_font;
const float font_size = GetOSDFontSize();
@@ -1002,12 +1002,12 @@ void ImGuiManager::DrawOSDMessages(Timer::Value current_time)
const float max_width_for_color = std::ceil(400.0f * scale);
const float min_rounded_width = rounding * 2.0f;
const bool show_messages = g_gpu_settings.display_show_messages;
float position_x = margin;
float position_y = margin;
const ImVec4 left_background_color = DarkerColor(UIStyle.ToastBackgroundColor, 1.3f);
const ImVec4 right_background_color = DarkerColor(UIStyle.ToastBackgroundColor, 0.8f);
FullscreenUI::NotificationLayout layout(g_gpu_settings.display_osd_message_location, spacing, margin);
auto iter = s_state.osd_active_messages.begin();
while (iter != s_state.osd_active_messages.end())
{
@@ -1051,69 +1051,56 @@ void ImGuiManager::DrawOSDMessages(Timer::Value current_time)
const float box_width = icon_size_with_margin + std::max(text_size.x, title_size.x) + padding + padding;
const float box_height = std::max(icon_size.y, title_size.y + text_size.y) + padding + padding;
float opacity;
float actual_x;
if (time_passed < OSD_FADE_IN_TIME)
const auto& [layout_pos, opacity] = layout.GetNextPosition(box_width, box_height, time_passed, msg.duration,
OSD_FADE_IN_TIME, OSD_FADE_OUT_TIME, 0.2f);
float actual_y;
if (!layout.IsVerticalAnimation() || opacity == 1.0f)
{
const float pct = time_passed / OSD_FADE_IN_TIME;
const float eased_pct = std::clamp(Easing::OutExpo(pct), 0.0f, 1.0f);
actual_x = ImFloor(position_x - (box_width * 0.2f * (1.0f - eased_pct)));
opacity = pct;
}
else if (time_passed > (msg.duration - OSD_FADE_OUT_TIME))
{
const float pct = (msg.duration - time_passed) / OSD_FADE_OUT_TIME;
const float eased_pct = std::clamp(Easing::InExpo(pct), 0.0f, 1.0f);
actual_x = ImFloor(position_x - (box_width * 0.2f * (1.0f - eased_pct)));
opacity = eased_pct;
actual_y = msg.last_y;
if (msg.target_y != layout_pos.y)
{
if (msg.last_y < 0.0f)
{
// First showing.
msg.last_y = layout_pos.y;
}
else
{
// We got repositioned, probably due to another message above getting removed.
const float time_since_move = static_cast<float>(Timer::ConvertValueToSeconds(current_time - msg.move_time));
const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
msg.last_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac));
}
msg.move_time = current_time;
msg.target_y = layout_pos.y;
actual_y = msg.last_y;
}
else if (actual_y != layout_pos.y)
{
const float time_since_move = static_cast<float>(Timer::ConvertValueToSeconds(current_time - msg.move_time));
if (time_since_move >= MOVE_DURATION)
{
msg.move_time = current_time;
msg.last_y = msg.target_y;
actual_y = msg.last_y;
}
else
{
const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
actual_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac));
}
}
}
else
{
opacity = 1.0f;
actual_x = position_x;
}
const float expected_y = position_y;
float actual_y = msg.last_y;
if (msg.target_y != expected_y)
{
if (msg.last_y < 0.0f)
{
// First showing.
msg.last_y = expected_y;
}
else
{
// We got repositioned, probably due to another message above getting removed.
const float time_since_move = static_cast<float>(Timer::ConvertValueToSeconds(current_time - msg.move_time));
const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
msg.last_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac));
}
msg.move_time = current_time;
msg.target_y = expected_y;
actual_y = msg.last_y;
}
else if (actual_y != expected_y)
{
const float time_since_move = static_cast<float>(Timer::ConvertValueToSeconds(current_time - msg.move_time));
if (time_since_move >= MOVE_DURATION)
{
msg.move_time = current_time;
msg.last_y = msg.target_y;
actual_y = msg.last_y;
}
else
{
const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
actual_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac));
}
actual_y = layout_pos.y;
}
if (actual_y >= ImGui::GetIO().DisplaySize.y || (msg.type >= OSDMessageType::Info && !show_messages))
break;
const ImVec2 pos = ImVec2(actual_x, actual_y);
const ImVec2 pos = ImVec2(layout_pos.x, actual_y);
const ImVec2 pos_max = ImVec2(pos.x + box_width, pos.y + box_height);
ImDrawList* const dl = ImGui::GetForegroundDrawList();
@@ -1157,11 +1144,9 @@ void ImGuiManager::DrawOSDMessages(Timer::Value current_time)
RenderShadowedTextClipped(dl, font, font_size, text_font_weight, text_rect.Min, text_rect.Max, actual_text_color,
msg.text, &text_size, ImVec2(0.0f, 0.0f), max_text_width, &text_rect, scale);
}
position_y += box_height + spacing;
}
s_state.osd_messages_end_y = position_y;
s_state.osd_messages_end_y = layout.GetCurrentPosition().y;
}
void ImGuiManager::RenderOSDMessages()