ProgressCallback: Add a variant with alert/confirm

And implement it in both Qt and FullscreenUI.
This commit is contained in:
Stenzek
2025-12-17 19:18:03 +10:00
parent 822024dda5
commit 97128838cf
6 changed files with 245 additions and 16 deletions

View File

@@ -101,3 +101,15 @@ void ProgressCallback::IncrementProgressValue()
{
SetProgressValue((m_progress_value - m_base_progress_value) + 1);
}
ProgressCallbackWithPrompt::~ProgressCallbackWithPrompt() = default;
void ProgressCallbackWithPrompt::AlertPrompt(PromptIcon icon, std::string_view message)
{
}
bool ProgressCallbackWithPrompt::ConfirmPrompt(PromptIcon icon, std::string_view message,
std::string_view yes_text /*= {}*/, std::string_view no_text /*= {}*/)
{
return false;
}

View File

@@ -68,3 +68,21 @@ protected:
public:
static ProgressCallback* NullProgressCallback;
};
class ProgressCallbackWithPrompt : public ProgressCallback
{
public:
virtual ~ProgressCallbackWithPrompt() override;
enum class PromptIcon
{
Error,
Warning,
Question,
Information,
};
virtual void AlertPrompt(PromptIcon icon, std::string_view message);
virtual bool ConfirmPrompt(PromptIcon icon, std::string_view message, std::string_view yes_text = {},
std::string_view no_text = {});
};

View File

@@ -263,12 +263,12 @@ public:
ProgressDialog();
~ProgressDialog();
std::unique_ptr<ProgressCallback> GetProgressCallback(std::string title, float window_unscaled_width);
std::unique_ptr<ProgressCallbackWithPrompt> GetProgressCallback(std::string title, float window_unscaled_width);
void Draw();
private:
class ProgressCallbackImpl : public ProgressCallback
class ProgressCallbackImpl : public ProgressCallbackWithPrompt
{
public:
ProgressCallbackImpl();
@@ -279,6 +279,10 @@ private:
void SetProgressValue(u32 value) override;
void SetCancellable(bool cancellable) override;
bool IsCancelled() const override;
void AlertPrompt(PromptIcon icon, std::string_view message) override;
bool ConfirmPrompt(PromptIcon icon, std::string_view message, std::string_view yes_text = {},
std::string_view no_text = {}) override;
};
std::string m_status_text;
@@ -287,6 +291,8 @@ private:
u32 m_progress_value = 0;
u32 m_progress_range = 0;
std::atomic_bool m_cancelled{false};
std::atomic_bool m_prompt_result{false};
std::atomic_flag m_prompt_waiting = ATOMIC_FLAG_INIT;
};
class FixedPopupDialog : public PopupDialog
@@ -337,12 +343,12 @@ struct WidgetsState
s32 enum_choice_button_value = 0;
bool enum_choice_button_set = false;
MessageDialog message_dialog;
ChoiceDialog choice_dialog;
FileSelectorDialog file_selector_dialog;
InputStringDialog input_string_dialog;
FixedPopupDialog fixed_popup_dialog;
ProgressDialog progress_dialog;
MessageDialog message_dialog;
ImAnimatedVec2 menu_button_frame_min_animated;
ImAnimatedVec2 menu_button_frame_max_animated;
@@ -1000,11 +1006,11 @@ void FullscreenUI::BeginLayout()
void FullscreenUI::EndLayout()
{
s_state.message_dialog.Draw();
s_state.choice_dialog.Draw();
s_state.file_selector_dialog.Draw();
s_state.input_string_dialog.Draw();
s_state.progress_dialog.Draw();
s_state.message_dialog.Draw();
DrawFullscreenFooter();
@@ -4106,14 +4112,14 @@ void FullscreenUI::ProgressDialog::Draw()
EndRender();
}
std::unique_ptr<ProgressCallback> FullscreenUI::ProgressDialog::GetProgressCallback(std::string title,
float window_unscaled_width)
std::unique_ptr<ProgressCallbackWithPrompt>
FullscreenUI::ProgressDialog::GetProgressCallback(std::string title, float window_unscaled_width)
{
if (m_state == PopupDialog::State::Open)
{
// return dummy callback so the op can still go through
ERROR_LOG("Progress dialog is already open, cannot create dialog for '{}'.", std::move(title));
return std::make_unique<ProgressCallback>();
return std::make_unique<ProgressCallbackWithPrompt>();
}
SetTitleAndOpen(std::move(title));
@@ -4200,7 +4206,96 @@ bool FullscreenUI::ProgressDialog::ProgressCallbackImpl::IsCancelled() const
return s_state.progress_dialog.m_cancelled.load(std::memory_order_acquire);
}
std::unique_ptr<ProgressCallback> FullscreenUI::OpenModalProgressDialog(std::string title, float window_unscaled_width)
void FullscreenUI::ProgressDialog::ProgressCallbackImpl::AlertPrompt(PromptIcon icon, std::string_view message)
{
s_state.progress_dialog.m_prompt_waiting.test_and_set(std::memory_order_release);
Host::RunOnCPUThread([message = std::string(message)]() mutable {
GPUThread::RunOnThread([message = std::move(message)]() mutable {
if (!s_state.progress_dialog.IsOpen())
{
s_state.progress_dialog.m_prompt_waiting.clear(std::memory_order_release);
s_state.progress_dialog.m_prompt_waiting.notify_one();
return;
}
// need to save state, since opening the next dialog will close this one
std::string existing_title = s_state.progress_dialog.GetTitle();
u32 progress_range = s_state.progress_dialog.m_progress_range;
u32 progress_value = s_state.progress_dialog.m_progress_value;
float last_frac = s_state.progress_dialog.m_last_frac;
float width = s_state.progress_dialog.m_width;
s_state.progress_dialog.CloseImmediately();
OpenInfoMessageDialog(
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;
s_state.progress_dialog.m_progress_value = progress_value;
s_state.progress_dialog.m_last_frac = last_frac;
s_state.progress_dialog.m_width = width;
s_state.progress_dialog.m_prompt_waiting.clear(std::memory_order_release);
s_state.progress_dialog.m_prompt_waiting.notify_one();
});
});
});
s_state.progress_dialog.m_prompt_waiting.wait(true, std::memory_order_acquire);
}
bool FullscreenUI::ProgressDialog::ProgressCallbackImpl::ConfirmPrompt(PromptIcon icon, std::string_view message,
std::string_view yes_text /* = */,
std::string_view no_text /* = */)
{
s_state.progress_dialog.m_prompt_result.store(false, std::memory_order_relaxed);
s_state.progress_dialog.m_prompt_waiting.test_and_set(std::memory_order_release);
Host::RunOnCPUThread(
[message = std::string(message), yes_text = std::string(yes_text), no_text = std::string(no_text)]() mutable {
GPUThread::RunOnThread(
[message = std::move(message), yes_text = std::move(yes_text), no_text = std::move(no_text)]() mutable {
if (!s_state.progress_dialog.IsOpen())
{
s_state.progress_dialog.m_prompt_waiting.clear(std::memory_order_release);
s_state.progress_dialog.m_prompt_waiting.notify_one();
return;
}
// need to save state, since opening the next dialog will close this one
std::string existing_title = s_state.progress_dialog.GetTitle();
u32 progress_range = s_state.progress_dialog.m_progress_range;
u32 progress_value = s_state.progress_dialog.m_progress_value;
float last_frac = s_state.progress_dialog.m_last_frac;
float width = s_state.progress_dialog.m_width;
s_state.progress_dialog.CloseImmediately();
if (yes_text.empty())
yes_text = FSUI_ICONSTR(ICON_FA_CHECK, "Yes");
if (no_text.empty())
no_text = FSUI_ICONSTR(ICON_FA_XMARK, "No");
OpenConfirmMessageDialog(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));
s_state.progress_dialog.m_progress_range = progress_range;
s_state.progress_dialog.m_progress_value = progress_value;
s_state.progress_dialog.m_last_frac = last_frac;
s_state.progress_dialog.m_width = width;
s_state.progress_dialog.m_prompt_result.store(result, std::memory_order_relaxed);
s_state.progress_dialog.m_prompt_waiting.clear(std::memory_order_release);
s_state.progress_dialog.m_prompt_waiting.notify_one();
});
});
});
s_state.progress_dialog.m_prompt_waiting.wait(true, std::memory_order_acquire);
return s_state.progress_dialog.m_prompt_result.load(std::memory_order_relaxed);
}
std::unique_ptr<ProgressCallbackWithPrompt> FullscreenUI::OpenModalProgressDialog(std::string title,
float window_unscaled_width)
{
return s_state.progress_dialog.GetProgressCallback(std::move(title), window_unscaled_width);
}

View File

@@ -27,7 +27,7 @@ class Error;
class Image;
class GPUTexture;
class GPUSwapChain;
class ProgressCallback;
class ProgressCallbackWithPrompt;
enum class OSDMessageType : u8;
@@ -484,7 +484,8 @@ void OpenMessageDialog(std::string_view title, std::string message, MessageDialo
std::string first_button_text, std::string second_button_text, std::string third_button_text);
void CloseMessageDialog();
std::unique_ptr<ProgressCallback> OpenModalProgressDialog(std::string title, float window_unscaled_width = 500.0f);
std::unique_ptr<ProgressCallbackWithPrompt> OpenModalProgressDialog(std::string title,
float window_unscaled_width = 500.0f);
float GetNotificationVerticalPosition();
float GetNotificationVerticalDirection();

View File

@@ -191,6 +191,9 @@ QtAsyncTaskWithProgressDialog::QtAsyncTaskWithProgressDialog(const QString& init
: m_callback(std::move(callback)), m_show_delay(show_delay)
{
m_dialog = new ProgressDialog(initial_title, initial_status_text, cancellable, range, value, *this, dialog_parent);
m_cancellable = cancellable;
m_progress_range = range;
m_progress_value = value;
if (show_delay <= 0.0f)
{
@@ -397,11 +400,94 @@ void QtAsyncTaskWithProgressDialog::SetProgressValue(u32 value)
}
}
void QtAsyncTaskWithProgressDialog::CheckForDelayedShow()
static QMessageBox::Icon ConvertPromptIcon(ProgressCallbackWithPrompt::PromptIcon icon)
{
DebugAssert(!m_shown);
switch (icon)
{
case ProgressCallbackWithPrompt::PromptIcon::Error:
return QMessageBox::Critical;
case ProgressCallbackWithPrompt::PromptIcon::Warning:
return QMessageBox::Warning;
case ProgressCallbackWithPrompt::PromptIcon::Question:
return QMessageBox::Question;
case ProgressCallbackWithPrompt::PromptIcon::Information:
default:
return QMessageBox::Information;
}
}
if (m_show_timer.GetTimeSeconds() < m_show_delay)
void QtAsyncTaskWithProgressDialog::AlertPrompt(PromptIcon icon, std::string_view message)
{
m_prompt_waiting.test_and_set(std::memory_order_release);
Host::RunOnUIThread([this, icon, message = QtUtils::StringViewToQString(message)]() {
if (!m_dialog)
{
// dialog closed :(
m_prompt_waiting.clear(std::memory_order_release);
m_prompt_waiting.notify_one();
return;
}
EnsureShown();
QMessageBox* msgbox =
QtUtils::NewMessageBox(m_dialog, ConvertPromptIcon(icon), m_dialog->windowTitle(), message, QMessageBox::Ok);
connect(msgbox, &QMessageBox::finished, [this]() {
m_prompt_waiting.clear(std::memory_order_release);
m_prompt_waiting.notify_one();
});
msgbox->open();
});
m_prompt_waiting.wait(true, std::memory_order_acquire);
}
bool QtAsyncTaskWithProgressDialog::ConfirmPrompt(PromptIcon icon, std::string_view message,
std::string_view yes_text /*= {}*/, std::string_view no_text /*= {}*/)
{
m_prompt_result.store(false, std::memory_order_relaxed);
m_prompt_waiting.test_and_set(std::memory_order_release);
Host::RunOnUIThread([this, icon, message = QtUtils::StringViewToQString(message),
yes_text = QtUtils::StringViewToQString(yes_text),
no_text = QtUtils::StringViewToQString(no_text)]() {
if (!m_dialog)
{
// dialog closed :(
m_prompt_waiting.clear(std::memory_order_release);
m_prompt_waiting.notify_one();
return;
}
EnsureShown();
QMessageBox* msgbox = QtUtils::NewMessageBox(m_dialog, ConvertPromptIcon(icon), m_dialog->windowTitle(), message,
QMessageBox::NoButton);
QAbstractButton* yes_button;
if (!yes_text.isEmpty())
yes_button = msgbox->addButton(yes_text, QMessageBox::YesRole);
else
yes_button = msgbox->addButton(QMessageBox::Yes);
if (!no_text.isEmpty())
msgbox->addButton(no_text, QMessageBox::NoRole);
else
msgbox->addButton(QMessageBox::No);
connect(msgbox, &QMessageBox::finished, [this, msgbox, yes_button]() {
m_prompt_result.store((msgbox->clickedButton() == yes_button), std::memory_order_relaxed);
m_prompt_waiting.clear(std::memory_order_release);
m_prompt_waiting.notify_one();
});
msgbox->open();
});
m_prompt_waiting.wait(true, std::memory_order_acquire);
return m_prompt_result.load(std::memory_order_relaxed);
}
void QtAsyncTaskWithProgressDialog::EnsureShown()
{
if (!m_shown)
return;
m_shown = true;
@@ -420,3 +506,13 @@ void QtAsyncTaskWithProgressDialog::CheckForDelayedShow()
m_dialog->open();
});
}
void QtAsyncTaskWithProgressDialog::CheckForDelayedShow()
{
DebugAssert(!m_shown);
if (m_show_timer.GetTimeSeconds() < m_show_delay)
return;
EnsureShown();
}

View File

@@ -88,13 +88,13 @@ private:
std::atomic_bool m_ts_cancelled{false};
};
class QtAsyncTaskWithProgressDialog final : public QObject, private ProgressCallback
class QtAsyncTaskWithProgressDialog final : public QObject, private ProgressCallbackWithPrompt
{
Q_OBJECT
public:
using CompletionCallback = std::function<void()>;
using WorkCallback = std::function<CompletionCallback(ProgressCallback*)>;
using WorkCallback = std::function<CompletionCallback(ProgressCallbackWithPrompt*)>;
static QtAsyncTaskWithProgressDialog* create(QWidget* parent, std::string_view initial_title,
std::string_view initial_status_text, bool cancellable, int range,
@@ -152,13 +152,20 @@ private:
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void AlertPrompt(PromptIcon icon, std::string_view message) override;
bool ConfirmPrompt(PromptIcon icon, std::string_view message, std::string_view yes_text = {},
std::string_view no_text = {}) override;
void CheckForDelayedShow();
void EnsureShown();
std::variant<WorkCallback, CompletionCallback> m_callback;
ProgressDialog* m_dialog = nullptr;
Timer m_show_timer;
float m_show_delay;
std::atomic_bool m_ts_cancelled{false};
bool m_shown = false;
std::atomic_bool m_ts_cancelled{false};
std::atomic_bool m_prompt_result{false};
std::atomic_flag m_prompt_waiting = ATOMIC_FLAG_INIT;
};