FullscreenUI: Use horizontal buttons for message dialogs

Unless it's a long string, then use vertical.

Also add icons to the dialogs.
This commit is contained in:
Stenzek
2026-01-12 20:00:00 +10:00
parent f5ebe1cee2
commit 1431d5cf5b
8 changed files with 120 additions and 54 deletions

View File

@@ -12,6 +12,7 @@
#define ICON_EMOJI_OPTICAL_DISK "\xf0\x9f\x92\xbf"
#define ICON_EMOJI_FLOPPY_DISK "\xf0\x9f\x92\xbe"
#define ICON_EMOJI_INFORMATION "\xe2\x84\xb9"
#define ICON_EMOJI_QUESTION_MARK "\xe2\x93\x98"
#define ICON_EMOJI_FAST_FORWARD "\xe2\x8f\xa9"
#define ICON_EMOJI_FAST_REVERSE "\xe2\x8f\xaa"
#define ICON_EMOJI_RECORD "\xe2\x8f\xba"

View File

@@ -639,7 +639,7 @@ void FullscreenUI::DoStartPath(std::string path, std::string state, std::optiona
if (!IsInitialized())
return;
OpenInfoMessageDialog(TRANSLATE_STR("System", "Error"),
OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, TRANSLATE_STR("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to boot system: {}"), error_desc));
ClearSaveStateEntryList();
UpdateRunIdleState();
@@ -730,7 +730,7 @@ void FullscreenUI::ConfirmIfSavingMemoryCards(std::string action, std::function<
}
OpenConfirmMessageDialog(
FSUI_ICONVSTR(ICON_PF_MEMORY_CARD, "Memory Card Busy"),
ICON_EMOJI_WARNING, FSUI_ICONVSTR(ICON_PF_MEMORY_CARD, "Memory Card Busy"),
fmt::format(
FSUI_FSTR("WARNING: Your game is still saving to the memory card. Continuing to {0} may IRREVERSIBLY "
"DESTROY YOUR MEMORY CARD. We recommend resuming your game and waiting 5 seconds for it to "

View File

@@ -101,7 +101,7 @@ void FullscreenUI::DoSetCoverImage(std::string entry_path)
if (!existing_path.empty())
{
OpenConfirmMessageDialog(
FSUI_ICONVSTR(ICON_FA_IMAGE, "Set Cover Image"),
ICON_EMOJI_WARNING, FSUI_ICONVSTR(ICON_FA_IMAGE, "Set Cover Image"),
FSUI_STR("A cover already exists for this game. Are you sure that you want to overwrite it?"),
[path = std::move(path), existing_path = std::move(existing_path),
new_path = std::move(new_path)](bool result) {

View File

@@ -1552,7 +1552,7 @@ void FullscreenUI::StartAutomaticBindingForPort(u32 port)
void FullscreenUI::StartClearBindingsForPort(u32 port)
{
OpenConfirmMessageDialog(
FSUI_STR("Clear Mappings"),
ICON_EMOJI_WARNING, FSUI_STR("Clear Mappings"),
FSUI_STR("Are you sure you want to clear all mappings for this controller?\n\nYou cannot undo this action."),
[port](bool result) {
if (!result)
@@ -1743,7 +1743,7 @@ void FullscreenUI::PopulatePatchesAndCheatsList()
void FullscreenUI::BeginResetSettings()
{
OpenConfirmMessageDialog(FSUI_STR("Restore Defaults"),
OpenConfirmMessageDialog(ICON_EMOJI_WARNING, FSUI_STR("Restore Defaults"),
FSUI_STR("Are you sure you want to restore the default settings? Any preferences will be "
"lost.\n\nYou cannot undo this action."),
[](bool result) {
@@ -2053,7 +2053,7 @@ void FullscreenUI::DrawSettingsWindow()
INFO_LOG("Removing empty game settings {}", s_settings_locals.game_settings_interface->GetPath());
if (!FileSystem::DeleteFile(s_settings_locals.game_settings_interface->GetPath().c_str(), &error))
{
OpenInfoMessageDialog(FSUI_STR("Error"),
OpenInfoMessageDialog(ICON_EMOJI_WARNING, FSUI_STR("Error"),
fmt::format(FSUI_FSTR("An error occurred while deleting empty game settings:\n{}"),
error.GetDescription()));
}
@@ -2064,7 +2064,7 @@ void FullscreenUI::DrawSettingsWindow()
if (!s_settings_locals.game_settings_interface->Save(&error))
{
OpenInfoMessageDialog(
FSUI_STR("Error"),
ICON_EMOJI_WARNING, FSUI_STR("Error"),
fmt::format(FSUI_FSTR("An error occurred while saving game settings:\n{}"), error.GetDescription()));
}
}
@@ -2613,7 +2613,7 @@ void FullscreenUI::DrawCoverDownloaderWindow()
});
}))
{
OpenInfoMessageDialog(FSUI_STR("Cover Download Error"), error.TakeDescription());
OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, FSUI_STR("Cover Download Error"), error.TakeDescription());
}
// close the parent window if we weren't cancelled
@@ -3117,7 +3117,7 @@ void FullscreenUI::DoSaveInputProfile()
void FullscreenUI::BeginResetControllerSettings()
{
OpenConfirmMessageDialog(FSUI_STR("Reset Controller Settings"),
OpenConfirmMessageDialog(ICON_EMOJI_WARNING, FSUI_STR("Reset Controller Settings"),
FSUI_STR("Are you sure you want to restore the default controller configuration?\n\nAll "
"bindings and configuration will be lost. You cannot undo this action."),
[](bool result) {
@@ -4312,7 +4312,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage()
if (MenuButton(FSUI_ICONVSTR(ICON_PF_TRASH, "Clear Shaders"), FSUI_VSTR("Clears a shader from the chain.")))
{
OpenConfirmMessageDialog(
FSUI_ICONVSTR(ICON_PF_TRASH, "Clear Shaders"),
ICON_EMOJI_WARNING, FSUI_ICONVSTR(ICON_PF_TRASH, "Clear Shaders"),
FSUI_STR("Are you sure you want to clear the current post-processing chain? All configuration will be lost."),
[](bool confirmed) {
if (!confirmed)
@@ -4880,7 +4880,7 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
if (Achievements::HasActiveGame())
{
OpenConfirmMessageDialog(
FSUI_ICONVSTR(ICON_FA_HAT_COWBOY, "Hardcore Mode"),
ICON_EMOJI_INFORMATION, FSUI_ICONVSTR(ICON_FA_HAT_COWBOY, "Hardcore Mode"),
FSUI_STR("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?"),
[](bool result) {
if (result)
@@ -5100,9 +5100,14 @@ void FullscreenUI::StartAchievementsProgressRefresh()
GPUThread::RunOnThread([error = std::move(error), progress, result]() mutable {
delete progress;
if (result)
{
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Progress database updated."));
}
else
FullscreenUI::OpenInfoMessageDialog(FSUI_STR("Update Progress"), error.TakeDescription());
{
FullscreenUI::OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, FSUI_STR("Update Progress"),
error.TakeDescription());
}
});
});
});
@@ -5119,9 +5124,14 @@ void FullscreenUI::StartAchievementsGameIconDownload()
GPUThread::RunOnThread([error = std::move(error), progress, result]() mutable {
delete progress;
if (result)
{
ShowToast(OSDMessageType::Info, {}, FSUI_STR("Game icons downloaded."));
}
else
FullscreenUI::OpenInfoMessageDialog(FSUI_STR("Download Game Icons"), error.TakeDescription());
{
FullscreenUI::OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, FSUI_STR("Download Game Icons"),
error.TakeDescription());
}
});
});
});

View File

@@ -168,8 +168,8 @@ public:
MessageDialog();
~MessageDialog();
void Open(std::string_view title, std::string message, CallbackVariant callback, std::string first_button_text,
std::string second_button_text, std::string third_button_text);
void Open(std::string_view icon, std::string_view title, std::string message, CallbackVariant callback,
std::string first_button_text, std::string second_button_text, std::string third_button_text);
void ClearState();
void Draw();
@@ -177,6 +177,7 @@ public:
private:
static void InvokeCallback(const CallbackVariant& cb, std::optional<s32> choice);
std::string m_icon;
std::string m_message;
std::array<std::string, 3> m_buttons;
CallbackVariant m_callback;
@@ -4209,11 +4210,12 @@ FullscreenUI::MessageDialog::MessageDialog() = default;
FullscreenUI::MessageDialog::~MessageDialog() = default;
void FullscreenUI::MessageDialog::Open(std::string_view title, std::string message, CallbackVariant callback,
std::string first_button_text, std::string second_button_text,
std::string third_button_text)
void FullscreenUI::MessageDialog::Open(std::string_view icon, std::string_view title, std::string message,
CallbackVariant callback, std::string first_button_text,
std::string second_button_text, std::string third_button_text)
{
SetTitleAndOpen(fmt::format("{}##message_dialog", title));
m_icon = icon;
m_message = std::move(message);
m_callback = std::move(callback);
m_buttons[0] = std::move(first_button_text);
@@ -4244,22 +4246,51 @@ void FullscreenUI::MessageDialog::Draw()
std::optional<s32> result;
ResetFocusHere();
BeginMenuButtons();
if (!m_icon.empty())
{
ImGui::PushFont(nullptr, LayoutScale(50.0f), 0.0f);
ImGui::TextUnformatted(IMSTR_START_END(m_icon));
ImGui::PopFont();
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + LayoutScale(10.0f));
}
ImGui::TextWrapped("%s", m_message.c_str());
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(20.0f));
for (s32 button_index = 0; button_index < static_cast<s32>(m_buttons.size()); button_index++)
{
if (!m_buttons[button_index].empty() &&
MenuButtonWithoutSummary(m_buttons[button_index], true, LAYOUT_CENTER_ALIGN_TEXT))
{
result = button_index;
}
}
ResetFocusHere();
EndMenuButtons();
// use vertical buttons for long text
if (!m_buttons[0].empty() && StringUtil::GetUTF8CharacterCount(m_buttons[0]) >= 15)
{
BeginMenuButtons();
for (s32 button_index = 0; button_index < static_cast<s32>(m_buttons.size()); button_index++)
{
if (!m_buttons[button_index].empty() &&
MenuButtonWithoutSummary(m_buttons[button_index], true, LAYOUT_CENTER_ALIGN_TEXT))
{
result = button_index;
}
}
EndMenuButtons();
}
else
{
BeginHorizontalMenuButtons(
static_cast<u32>(std::ranges::count_if(m_buttons, [](const std::string& str) { return !str.empty(); })));
for (s32 button_index = 0; button_index < static_cast<s32>(m_buttons.size()); button_index++)
{
if (!m_buttons[button_index].empty() &&
HorizontalMenuButton(m_buttons[button_index], true, LAYOUT_CENTER_ALIGN_TEXT))
{
result = button_index;
}
}
EndHorizontalMenuButtons();
}
SetStandardSelectionFooterText(false);
@@ -4294,27 +4325,28 @@ bool FullscreenUI::IsMessageBoxDialogOpen()
return s_state.message_dialog.IsOpen();
}
void FullscreenUI::OpenConfirmMessageDialog(std::string_view title, std::string message,
void FullscreenUI::OpenConfirmMessageDialog(std::string_view icon, std::string_view title, std::string message,
ConfirmMessageDialogCallback callback, std::string yes_button_text,
std::string no_button_text)
{
s_state.message_dialog.Open(std::move(title), std::move(message), std::move(callback), std::move(yes_button_text),
std::move(no_button_text), std::string());
s_state.message_dialog.Open(icon, std::move(title), std::move(message), std::move(callback),
std::move(yes_button_text), std::move(no_button_text), std::string());
}
void FullscreenUI::OpenInfoMessageDialog(std::string_view title, std::string message,
void FullscreenUI::OpenInfoMessageDialog(std::string_view icon, std::string_view title, std::string message,
InfoMessageDialogCallback callback, std::string button_text)
{
s_state.message_dialog.Open(std::move(title), std::move(message), std::move(callback), std::move(button_text),
s_state.message_dialog.Open(icon, std::move(title), std::move(message), std::move(callback), std::move(button_text),
std::string(), std::string());
}
void FullscreenUI::OpenMessageDialog(std::string_view title, std::string message, MessageDialogCallback callback,
std::string first_button_text, std::string second_button_text,
std::string third_button_text)
void FullscreenUI::OpenMessageDialog(std::string_view icon, std::string_view title, std::string message,
MessageDialogCallback callback, std::string first_button_text,
std::string second_button_text, std::string third_button_text)
{
s_state.message_dialog.Open(std::move(title), std::move(message), std::move(callback), std::move(first_button_text),
std::move(second_button_text), std::move(third_button_text));
s_state.message_dialog.Open(icon, std::move(title), std::move(message), std::move(callback),
std::move(first_button_text), std::move(second_button_text),
std::move(third_button_text));
}
void FullscreenUI::CloseMessageDialog()
@@ -4501,8 +4533,8 @@ void FullscreenUI::ProgressDialog::ProgressCallbackImpl::AlertPrompt(PromptIcon
{
s_state.progress_dialog.m_prompt_waiting.test_and_set(std::memory_order_release);
Host::RunOnCoreThread([message = std::string(message)]() mutable {
GPUThread::RunOnThread([message = std::move(message)]() mutable {
Host::RunOnCoreThread([message = std::string(message), icon]() mutable {
GPUThread::RunOnThread([message = std::move(message), icon]() mutable {
if (!s_state.progress_dialog.IsOpen())
{
s_state.progress_dialog.m_prompt_waiting.clear(std::memory_order_release);
@@ -4518,8 +4550,26 @@ void FullscreenUI::ProgressDialog::ProgressCallbackImpl::AlertPrompt(PromptIcon
float width = s_state.progress_dialog.m_width;
s_state.progress_dialog.CloseImmediately();
std::string_view icon_str;
switch (icon)
{
case PromptIcon::Error:
icon_str = ICON_EMOJI_NO_ENTRY_SIGN;
break;
case PromptIcon::Warning:
icon_str = ICON_EMOJI_WARNING;
break;
case PromptIcon::Question:
icon_str = ICON_EMOJI_QUESTION_MARK;
break;
case PromptIcon::Information:
default:
icon_str = ICON_EMOJI_INFORMATION;
break;
}
OpenInfoMessageDialog(
s_state.progress_dialog.GetTitle(), std::move(message),
icon_str, s_state.progress_dialog.GetTitle(), std::move(message),
[existing_title = std::move(existing_title), progress_range, progress_value, last_frac, width]() mutable {
s_state.progress_dialog.SetTitleAndOpen(std::move(existing_title));
s_state.progress_dialog.m_progress_range = progress_range;
@@ -4566,7 +4616,7 @@ bool FullscreenUI::ProgressDialog::ProgressCallbackImpl::ConfirmPrompt(PromptIco
if (no_text.empty())
no_text = FSUI_ICONSTR(ICON_FA_XMARK, "No");
OpenConfirmMessageDialog(s_state.progress_dialog.GetTitle(), std::move(message),
OpenConfirmMessageDialog(ICON_EMOJI_QUESTION_MARK, s_state.progress_dialog.GetTitle(), std::move(message),
[existing_title = std::move(existing_title), progress_range, progress_value,
last_frac, width](bool result) mutable {
s_state.progress_dialog.SetTitleAndOpen(std::move(existing_title));

View File

@@ -10,6 +10,7 @@
#include "common/small_string.h"
#include "common/types.h"
#include "IconsEmoji.h"
#include "IconsFontAwesome.h"
#include "imgui.h"
#include "imgui_internal.h"
@@ -510,13 +511,16 @@ using ConfirmMessageDialogCallback = std::function<void(bool)>;
using InfoMessageDialogCallback = std::function<void()>;
using MessageDialogCallback = std::function<void(s32)>;
bool IsMessageBoxDialogOpen();
void OpenConfirmMessageDialog(std::string_view title, std::string message, ConfirmMessageDialogCallback callback,
void OpenConfirmMessageDialog(std::string_view icon, std::string_view title, std::string message,
ConfirmMessageDialogCallback callback,
std::string yes_button_text = FSUI_ICONSTR(ICON_FA_CHECK, "Yes"),
std::string no_button_text = FSUI_ICONSTR(ICON_FA_XMARK, "No"));
void OpenInfoMessageDialog(std::string_view title, std::string message, InfoMessageDialogCallback callback = {},
void OpenInfoMessageDialog(std::string_view icon, std::string_view title, std::string message,
InfoMessageDialogCallback callback = {},
std::string button_text = FSUI_ICONSTR(ICON_FA_SQUARE_XMARK, "Close"));
void OpenMessageDialog(std::string_view title, std::string message, MessageDialogCallback callback,
std::string first_button_text, std::string second_button_text, std::string third_button_text);
void OpenMessageDialog(std::string_view icon, std::string_view title, std::string message,
MessageDialogCallback callback, std::string first_button_text, std::string second_button_text,
std::string third_button_text);
void CloseMessageDialog();
std::unique_ptr<ProgressCallbackWithPrompt> OpenModalProgressDialog(std::string title,

View File

@@ -1071,9 +1071,10 @@ void Host::OnSystemAbnormalShutdown(const std::string_view reason)
{
GPUThread::RunOnThread([reason = std::string(reason)]() {
FullscreenUI::OpenInfoMessageDialog(
"Abnormal System Shutdown", fmt::format("Unfortunately, the virtual machine has abnormally shut down and cannot "
"be recovered. More information about the error is below:\n\n{}",
reason));
ICON_EMOJI_NO_ENTRY_SIGN, "Abnormal System Shutdown",
fmt::format("Unfortunately, the virtual machine has abnormally shut down and cannot "
"be recovered. More information about the error is below:\n\n{}",
reason));
});
}
@@ -1460,8 +1461,8 @@ void Host::ConfirmMessageAsync(std::string_view title, std::string_view message,
callback(result);
};
FullscreenUI::OpenConfirmMessageDialog(std::move(title), std::move(message), std::move(final_callback),
fmt::format(ICON_FA_CHECK " {}", yes_text),
FullscreenUI::OpenConfirmMessageDialog(ICON_EMOJI_QUESTION_MARK, std::move(title), std::move(message),
std::move(final_callback), fmt::format(ICON_FA_CHECK " {}", yes_text),
fmt::format(ICON_FA_XMARK " {}", no_text));
FullscreenUI::UpdateRunIdleState();
});

View File

@@ -2136,8 +2136,8 @@ void Host::ConfirmMessageAsync(std::string_view title, std::string_view message,
callback(result);
};
FullscreenUI::OpenConfirmMessageDialog(std::move(title), std::move(message), std::move(final_callback),
fmt::format(ICON_FA_CHECK " {}", yes_text),
FullscreenUI::OpenConfirmMessageDialog(ICON_EMOJI_QUESTION_MARK, std::move(title), std::move(message),
std::move(final_callback), fmt::format(ICON_FA_CHECK " {}", yes_text),
fmt::format(ICON_FA_XMARK " {}", no_text));
FullscreenUI::UpdateRunIdleState();
});