mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-11 01:24:11 +02:00
Host: Add icon to ConfirmMessageAsync()
And use it for achievements.
This commit is contained in:
@@ -2396,7 +2396,7 @@ void Achievements::Logout()
|
||||
void Achievements::ConfirmHardcoreModeDisableAsync(std::string_view trigger, std::function<void(bool)> callback)
|
||||
{
|
||||
Host::ConfirmMessageAsync(
|
||||
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode Disable"),
|
||||
RA_LOGO_SVG_ICON_NAME, TRANSLATE_STR("Achievements", "Confirm Hardcore Mode Disable"),
|
||||
fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you want to "
|
||||
"disable hardcore mode? {0} will be cancelled if you select No."),
|
||||
trigger),
|
||||
|
||||
@@ -207,7 +207,7 @@ public:
|
||||
MessageDialog();
|
||||
~MessageDialog();
|
||||
|
||||
void Open(std::string_view icon, std::string_view title, std::string message, CallbackVariant callback,
|
||||
void Open(std::string 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();
|
||||
|
||||
@@ -350,7 +350,7 @@ private:
|
||||
std::string_view no_text = {}) override;
|
||||
|
||||
protected:
|
||||
static std::string_view GetIconString(PromptIcon icon);
|
||||
static std::string GetIconString(PromptIcon icon);
|
||||
|
||||
void StateChanged(StateChange changed) override;
|
||||
};
|
||||
@@ -5438,12 +5438,12 @@ FullscreenUI::MessageDialog::MessageDialog() = default;
|
||||
|
||||
FullscreenUI::MessageDialog::~MessageDialog() = default;
|
||||
|
||||
void FullscreenUI::MessageDialog::Open(std::string_view icon, std::string_view title, std::string message,
|
||||
void FullscreenUI::MessageDialog::Open(std::string 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_icon = std::move(icon);
|
||||
m_message = std::move(message);
|
||||
m_callback = std::move(callback);
|
||||
m_buttons[0] = std::move(first_button_text);
|
||||
@@ -5476,9 +5476,17 @@ void FullscreenUI::MessageDialog::Draw()
|
||||
|
||||
if (!m_icon.empty())
|
||||
{
|
||||
ImGui::PushFont(nullptr, LayoutScale(50.0f), 0.0f);
|
||||
ImGui::TextUnformatted(IMSTR_START_END(m_icon));
|
||||
ImGui::PopFont();
|
||||
const ImVec2 icon_size = LayoutScale(52.0f, 52.0f);
|
||||
if (Path::IsAbsolute(m_icon))
|
||||
{
|
||||
ImGui::Image(GetCachedTexture(m_icon, icon_size), icon_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::PushFont(nullptr, LayoutScale(52.0f), 0.0f);
|
||||
ImGui::TextUnformatted(IMSTR_START_END(m_icon));
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + LayoutScale(10.0f));
|
||||
}
|
||||
@@ -5553,26 +5561,26 @@ bool FullscreenUI::IsMessageBoxDialogOpen()
|
||||
return s_state.message_dialog.IsOpen();
|
||||
}
|
||||
|
||||
void FullscreenUI::OpenConfirmMessageDialog(std::string_view icon, std::string_view title, std::string message,
|
||||
void FullscreenUI::OpenConfirmMessageDialog(std::string 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(icon, std::move(title), std::move(message), std::move(callback),
|
||||
s_state.message_dialog.Open(std::move(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 icon, std::string_view title, std::string message,
|
||||
void FullscreenUI::OpenInfoMessageDialog(std::string icon, std::string_view title, std::string message,
|
||||
InfoMessageDialogCallback callback, std::string 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());
|
||||
s_state.message_dialog.Open(std::move(icon), std::move(title), std::move(message), std::move(callback),
|
||||
std::move(button_text), std::string(), std::string());
|
||||
}
|
||||
|
||||
void FullscreenUI::OpenMessageDialog(std::string_view icon, std::string_view title, std::string message,
|
||||
void FullscreenUI::OpenMessageDialog(std::string 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(icon, std::move(title), std::move(message), std::move(callback),
|
||||
s_state.message_dialog.Open(std::move(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));
|
||||
}
|
||||
@@ -5744,7 +5752,7 @@ bool FullscreenUI::ProgressDialog::ProgressCallbackImpl::IsCancelled() const
|
||||
return s_state.progress_dialog.m_cancelled.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
std::string_view FullscreenUI::ProgressDialog::ProgressCallbackImpl::GetIconString(PromptIcon icon)
|
||||
std::string FullscreenUI::ProgressDialog::ProgressCallbackImpl::GetIconString(PromptIcon icon)
|
||||
{
|
||||
switch (icon)
|
||||
{
|
||||
|
||||
@@ -566,16 +566,15 @@ using ConfirmMessageDialogCallback = std::function<void(bool)>;
|
||||
using InfoMessageDialogCallback = std::function<void()>;
|
||||
using MessageDialogCallback = std::function<void(s32)>;
|
||||
bool IsMessageBoxDialogOpen();
|
||||
void OpenConfirmMessageDialog(std::string_view icon, std::string_view title, std::string message,
|
||||
void OpenConfirmMessageDialog(std::string 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 icon, std::string_view title, std::string message,
|
||||
void OpenInfoMessageDialog(std::string 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 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 OpenMessageDialog(std::string 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,
|
||||
|
||||
+3
-2
@@ -49,8 +49,9 @@ void ReportStatusMessage(std::string_view message);
|
||||
/// Displays an asynchronous confirmation on the UI thread, but does not block the caller.
|
||||
/// The callback may be executed on a different thread. Use RunOnCoreThread() in the callback to ensure safety.
|
||||
using ConfirmMessageAsyncCallback = std::function<void(bool)>;
|
||||
void ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback,
|
||||
std::string_view yes_text = std::string_view(), std::string_view no_text = std::string_view());
|
||||
void ConfirmMessageAsync(std::string_view icon, std::string_view title, std::string_view message,
|
||||
ConfirmMessageAsyncCallback callback, std::string_view yes_text = std::string_view(),
|
||||
std::string_view no_text = std::string_view());
|
||||
|
||||
/// Opens a URL, using the default application.
|
||||
void OpenURL(std::string_view url);
|
||||
|
||||
+1
-1
@@ -1684,7 +1684,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
||||
if (Core::GetBoolSettingValue("CDROM", "AllowBootingWithoutSBIFile", false))
|
||||
{
|
||||
Host::ConfirmMessageAsync(
|
||||
"Confirm Unsupported Configuration",
|
||||
"images/warning.svg", "Confirm Unsupported Configuration",
|
||||
fmt::format(TRANSLATE_FS("System",
|
||||
"You are attempting to run a libcrypt protected game without an SBI file:\n\n{0}: "
|
||||
"{1}\n\nThe game will likely not run properly.\n\nPlease check the README for "
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "qtwindowinfo.h"
|
||||
#include "settingswindow.h"
|
||||
#include "setupwizarddialog.h"
|
||||
#include "svgwidget.h"
|
||||
|
||||
#include "core/achievements.h"
|
||||
#include "core/bus.h"
|
||||
@@ -2351,8 +2352,9 @@ void Host::ReportStatusMessage(std::string_view message)
|
||||
emit g_core_thread->statusMessage(QtUtils::StringViewToQString(message));
|
||||
}
|
||||
|
||||
void Host::ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback,
|
||||
std::string_view yes_text, std::string_view no_text)
|
||||
void Host::ConfirmMessageAsync(std::string_view icon, std::string_view title, std::string_view message,
|
||||
ConfirmMessageAsyncCallback callback, std::string_view yes_text,
|
||||
std::string_view no_text)
|
||||
{
|
||||
INFO_LOG("ConfirmMessageAsync({}, {})", title, message);
|
||||
|
||||
@@ -2365,9 +2367,10 @@ void Host::ConfirmMessageAsync(std::string_view title, std::string_view message,
|
||||
// Ensure it always comes from the CPU thread.
|
||||
if (!g_core_thread->isCurrentThread())
|
||||
{
|
||||
Host::RunOnCoreThread([title = std::string(title), message = std::string(message), callback = std::move(callback),
|
||||
yes_text = std::string(yes_text), no_text = std::string(no_text)]() mutable {
|
||||
ConfirmMessageAsync(title, message, std::move(callback));
|
||||
Host::RunOnCoreThread([title = std::string(title), icon = std::string(icon), message = std::string(message),
|
||||
callback = std::move(callback), yes_text = std::string(yes_text),
|
||||
no_text = std::string(no_text)]() mutable {
|
||||
ConfirmMessageAsync(icon, title, message, std::move(callback), yes_text, no_text);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -2380,7 +2383,7 @@ void Host::ConfirmMessageAsync(std::string_view title, std::string_view message,
|
||||
// Use FSUI if we're ingame.
|
||||
if (System::IsValid() || g_core_thread->isFullscreenUIStarted())
|
||||
{
|
||||
VideoThread::RunOnThread([title = std::string(title), message = std::string(message),
|
||||
VideoThread::RunOnThread([title = std::string(title), icon = std::string(icon), message = std::string(message),
|
||||
callback = std::move(callback), yes_text = std::string(yes_text),
|
||||
no_text = std::string(no_text), needs_pause]() mutable {
|
||||
// Need to reset run idle state _again_ after displaying.
|
||||
@@ -2396,8 +2399,13 @@ void Host::ConfirmMessageAsync(std::string_view title, std::string_view message,
|
||||
callback(result);
|
||||
};
|
||||
|
||||
if (icon.empty())
|
||||
icon = ICON_EMOJI_QUESTION_MARK;
|
||||
else if (StringUtil::GetUTF8CharacterCount(icon) > 1 && !Path::IsAbsolute(icon))
|
||||
icon = QtHost::GetResourcePath(icon, true);
|
||||
|
||||
FullscreenUI::Initialize();
|
||||
FullscreenUI::OpenConfirmMessageDialog(ICON_EMOJI_QUESTION_MARK, std::move(title), std::move(message),
|
||||
FullscreenUI::OpenConfirmMessageDialog(std::move(icon), 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();
|
||||
@@ -2405,15 +2413,22 @@ void Host::ConfirmMessageAsync(std::string_view title, std::string_view message,
|
||||
}
|
||||
else
|
||||
{
|
||||
QString qicon;
|
||||
if (!icon.empty())
|
||||
qicon = Path::IsAbsolute(icon) ? QtUtils::StringViewToQString(icon) : QtHost::GetResourceQPath(icon, true);
|
||||
|
||||
// Otherwise, use the desktop UI.
|
||||
Host::RunOnUIThread([title = QtUtils::StringViewToQString(title), message = QtUtils::StringViewToQString(message),
|
||||
callback = std::move(callback), yes_text = QtUtils::StringViewToQString(yes_text),
|
||||
Host::RunOnUIThread([qicon = std::move(qicon), title = QtUtils::StringViewToQString(title),
|
||||
message = QtUtils::StringViewToQString(message), callback = std::move(callback),
|
||||
yes_text = QtUtils::StringViewToQString(yes_text),
|
||||
no_text = QtUtils::StringViewToQString(no_text), needs_pause]() mutable {
|
||||
auto lock = g_main_window->pauseAndLockSystem();
|
||||
|
||||
QWidget* const dialog_parent = lock.getDialogParent();
|
||||
QMessageBox* const msgbox =
|
||||
QtUtils::NewMessageBox(dialog_parent, QMessageBox::Question, title, message, QMessageBox::NoButton);
|
||||
if (!qicon.isEmpty())
|
||||
msgbox->setIconPixmap(SVGWidget::renderSVGToPixmap(qicon, QSize(64, 64), msgbox->devicePixelRatio(), QColor()));
|
||||
|
||||
QPushButton* const yes_button = msgbox->addButton(yes_text, QMessageBox::AcceptRole);
|
||||
msgbox->addButton(no_text, QMessageBox::RejectRole);
|
||||
|
||||
@@ -179,4 +179,67 @@ void SVGWidget::changeEvent(QEvent* event)
|
||||
rasterize();
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap SVGWidget::renderSVGToPixmap(const QString& resource_path, const QSize& size, qreal device_pixel_ratio,
|
||||
const QColor& color)
|
||||
{
|
||||
DynamicHeapArray<u8> svg_data;
|
||||
QFile file(resource_path);
|
||||
if (!file.open(QFile::ReadOnly) || !QtUtils::ReadFileToByteArray(&file, svg_data))
|
||||
{
|
||||
qCritical() << "Failed to open SVG file: " << resource_path;
|
||||
return {};
|
||||
}
|
||||
|
||||
// plutosvg borrows the raw pointer; svg_data must outlive m_document.
|
||||
plutosvg_document* const document = plutosvg_document_load_from_data(
|
||||
reinterpret_cast<const char*>(svg_data.data()), static_cast<int>(svg_data.size()), -1.0f, -1.0f, nullptr, nullptr);
|
||||
if (!document)
|
||||
{
|
||||
qCritical() << "PlutoSVGWidget: failed to parse SVG" << resource_path;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Determine SVG intrinsic size and compute a uniform scale that fits inside physical,
|
||||
// preserving aspect ratio.
|
||||
const float svg_w = plutosvg_document_get_width(document);
|
||||
const float svg_h = plutosvg_document_get_height(document);
|
||||
|
||||
const QSize physical = QtUtils::ApplyDevicePixelRatioToSize(size, device_pixel_ratio);
|
||||
int render_w = physical.width();
|
||||
int render_h = physical.height();
|
||||
|
||||
if (svg_w > 0.0f && svg_h > 0.0f)
|
||||
{
|
||||
// Scale to fit, preserving aspect ratio (letterbox / pillarbox).
|
||||
const float scale_x = static_cast<float>(physical.width()) / svg_w;
|
||||
const float scale_y = static_cast<float>(physical.height()) / svg_h;
|
||||
const float scale = std::min(scale_x, scale_y);
|
||||
render_w = std::max(1, static_cast<int>(svg_w * scale));
|
||||
render_h = std::max(1, static_cast<int>(svg_h * scale));
|
||||
}
|
||||
|
||||
// Use white as currentColor so the SVG's own colours are preserved.
|
||||
// Swap in a palette colour here if tinting is ever desired.
|
||||
const plutovg_color_t current_color = {.r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f};
|
||||
|
||||
plutovg_surface_t* const surface =
|
||||
plutosvg_document_render_to_surface(document, nullptr, render_w, render_h, ¤t_color, nullptr, nullptr);
|
||||
if (!surface)
|
||||
{
|
||||
qCritical() << "PlutoSVGWidget: render failed for" << resource_path;
|
||||
plutosvg_document_destroy(document);
|
||||
return {};
|
||||
}
|
||||
|
||||
// plutovg surfaces are premultiplied ARGB (native-endian) == QImage::Format_ARGB32_Premultiplied.
|
||||
const QImage img(plutovg_surface_get_data(surface), plutovg_surface_get_width(surface),
|
||||
plutovg_surface_get_height(surface), plutovg_surface_get_stride(surface),
|
||||
QImage::Format_ARGB32_Premultiplied, CleanupSurface, surface);
|
||||
|
||||
QPixmap pm = QPixmap::fromImage(img);
|
||||
pm.setDevicePixelRatio(device_pixel_ratio);
|
||||
plutosvg_document_destroy(document);
|
||||
return pm;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ public:
|
||||
/// Returns the path that was passed to setSource().
|
||||
const QString& source() const { return m_resource_path; }
|
||||
|
||||
/// Renders a SVG for the specified widget and returns it.
|
||||
static QPixmap renderSVGToPixmap(const QString& resource_path, const QSize& size, qreal device_pixel_ratio,
|
||||
const QColor& color = QColor());
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
@@ -131,8 +131,9 @@ void Host::ReportStatusMessage(std::string_view message)
|
||||
INFO_LOG("ReportStatusMessage: {}", message);
|
||||
}
|
||||
|
||||
void Host::ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback,
|
||||
std::string_view yes_text, std::string_view no_text)
|
||||
void Host::ConfirmMessageAsync(std::string_view icon, std::string_view title, std::string_view message,
|
||||
ConfirmMessageAsyncCallback callback, std::string_view yes_text,
|
||||
std::string_view no_text)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
ERROR_LOG("ConfirmMessage: {}: {}", title, message);
|
||||
|
||||
Reference in New Issue
Block a user