mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
ProgressCallback: Add a variant with alert/confirm
And implement it in both Qt and FullscreenUI.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = {});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user