From a9862461f3a1c650a41a3200b94f2db359f0f28e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 19 Jul 2025 17:07:34 +1000 Subject: [PATCH] FullscreenUI: Add modal progress callback --- src/util/imgui_fullscreen.cpp | 211 +++++++++++++++++++++++++++++++++- src/util/imgui_fullscreen.h | 6 +- 2 files changed, 215 insertions(+), 2 deletions(-) diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index bf58e21f0..c7b59ab84 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -19,6 +19,7 @@ #include "common/timer.h" #include "core/fullscreen_ui.h" // For updating run idle state. +#include "core/gpu_thread.h" // For kicking to GPU thread. #include "core/host.h" #include "core/system.h" // For async workers, should be in general host. @@ -192,6 +193,38 @@ private: InputStringDialogCallback m_callback; }; +class ProgressDialog : public PopupDialog +{ +public: + ProgressDialog(); + ~ProgressDialog(); + + std::unique_ptr GetProgressCallback(std::string title, float window_unscaled_width); + + void Draw(); + +private: + class ProgressCallbackImpl : public ProgressCallback + { + public: + ProgressCallbackImpl(); + ~ProgressCallbackImpl() override; + + void SetStatusText(std::string_view text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + void SetCancellable(bool cancellable) override; + bool IsCancelled() const override; + }; + + std::string m_status_text; + float m_last_frac = 0.0f; + float m_width = 0.0f; + u32 m_progress_value = 0; + u32 m_progress_range = 0; + std::atomic_bool m_cancelled{false}; +}; + class FixedPopupDialog : public PopupDialog { public: @@ -238,6 +271,7 @@ struct ALIGN_TO_CACHE_LINE UIState FileSelectorDialog file_selector_dialog; InputStringDialog input_string_dialog; FixedPopupDialog fixed_popup_dialog; + ProgressDialog progress_dialog; ImAnimatedVec2 menu_button_frame_min_animated; ImAnimatedVec2 menu_button_frame_max_animated; @@ -709,6 +743,7 @@ void ImGuiFullscreen::EndLayout() s_state.choice_dialog.Draw(); s_state.file_selector_dialog.Draw(); s_state.input_string_dialog.Draw(); + s_state.progress_dialog.Draw(); DrawFullscreenFooter(); @@ -2863,7 +2898,7 @@ bool ImGuiFullscreen::PopupDialog::BeginRender(float scaled_window_padding /* = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | (m_title.starts_with("##") ? ImGuiWindowFlags_NoTitleBar : 0); bool is_open = true; - if (popup_open && !ImGui::Begin(m_title.c_str(), &is_open, window_flags)) + if (popup_open && !ImGui::Begin(m_title.c_str(), m_user_closeable ? &is_open : nullptr, window_flags)) is_open = false; if (popup_open && !is_open && m_state != State::ClosingTrigger) @@ -3499,6 +3534,180 @@ void ImGuiFullscreen::CloseMessageDialog() s_state.message_dialog.StartClose(); } +ImGuiFullscreen::ProgressDialog::ProgressDialog() = default; +ImGuiFullscreen::ProgressDialog::~ProgressDialog() = default; + +void ImGuiFullscreen::ProgressDialog::Draw() +{ + if (!IsOpen()) + return; + + const float window_padding = LayoutScale(20.0f); + + if (!BeginRender(window_padding, window_padding, ImVec2(m_width, 0.0f))) + { + if (m_user_closeable) + m_cancelled.store(true, std::memory_order_release); + + m_status_text = {}; + m_last_frac = 0.0f; + ClearState(); + return; + } + + const float spacing = LayoutScale(5.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, spacing)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, DarkerColor(UIStyle.PopupBackgroundColor)); + ImGui::PushStyleColor(ImGuiCol_Text, UIStyle.BackgroundTextColor); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, UIStyle.SecondaryColor); + ImGui::PushFont(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.NormalFontWeight); + + const bool has_progress = (m_progress_range > 0); + float wrap_width = ImGui::GetContentRegionAvail().x; + if (has_progress) + { + // reserve space for text + TinyString text; + text.format("{}/{}", m_progress_value, m_progress_range); + + const ImVec2 text_width = ImGui::CalcTextSize(IMSTR_START_END(text)); + const ImVec2 screen_pos = ImGui::GetCursorScreenPos(); + const ImVec2 text_pos = ImVec2(screen_pos.x + wrap_width - text_width.x, screen_pos.y); + ImGui::GetWindowDrawList()->AddText(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight, text_pos, + ImGui::GetColorU32(ImGuiCol_Text), IMSTR_START_END(text)); + wrap_width -= text_width.x + spacing; + } + + if (!m_status_text.empty()) + ImGuiFullscreen::TextAlignedMultiLine(0.0f, IMSTR_START_END(m_status_text), wrap_width); + + const float bar_height = LayoutScale(20.0f); + + float frac; + if (has_progress) + { + const float max_frac = (static_cast(m_progress_value) / static_cast(m_progress_range)); + const float dt = ImGui::GetIO().DeltaTime; + frac = std::min(m_last_frac + dt, max_frac); + m_last_frac = frac; + } + else + { + frac = static_cast(-ImGui::GetTime()); + } + ImGui::ProgressBar(frac, ImVec2(-1.0f, bar_height), ""); + + ImGui::Dummy(ImVec2(0.0f, LayoutScale(5.0f))); + + ImGui::PopFont(); + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); + + if (m_user_closeable) + { + BeginHorizontalMenuButtons(1, 150.0f); + if (HorizontalMenuButton("Cancel")) + StartClose(); + EndHorizontalMenuButtons(); + } + + EndRender(); +} + +std::unique_ptr ImGuiFullscreen::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(); + } + + SetTitleAndOpen(std::move(title)); + m_width = LayoutScale(window_unscaled_width); + m_last_frac = 0.0f; + m_cancelled.store(false, std::memory_order_release); + return std::make_unique(); +} + +ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::ProgressCallbackImpl() = default; + +ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::~ProgressCallbackImpl() +{ + Host::RunOnCPUThread([]() mutable { + GPUThread::RunOnThread([]() mutable { + if (!s_state.progress_dialog.IsOpen()) + return; + s_state.progress_dialog.StartClose(); + }); + }); +} + +void ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::SetStatusText(std::string_view text) +{ + Host::RunOnCPUThread([text = std::string(text)]() mutable { + GPUThread::RunOnThread([text = std::move(text)]() mutable { + if (!s_state.progress_dialog.IsOpen()) + return; + + s_state.progress_dialog.m_status_text = std::move(text); + }); + }); +} + +void ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::SetProgressRange(u32 range) +{ + ProgressCallback::SetProgressRange(range); + + Host::RunOnCPUThread([range]() { + GPUThread::RunOnThread([range]() { + if (!s_state.progress_dialog.IsOpen()) + return; + + s_state.progress_dialog.m_progress_range = range; + }); + }); +} + +void ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::SetProgressValue(u32 value) +{ + ProgressCallback::SetProgressValue(value); + + Host::RunOnCPUThread([value]() { + GPUThread::RunOnThread([value]() { + if (!s_state.progress_dialog.IsOpen()) + return; + + s_state.progress_dialog.m_progress_value = value; + }); + }); +} + +void ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::SetCancellable(bool cancellable) +{ + ProgressCallback::SetCancellable(cancellable); + + Host::RunOnCPUThread([cancellable]() { + GPUThread::RunOnThread([cancellable]() { + if (!s_state.progress_dialog.IsOpen()) + return; + + s_state.progress_dialog.m_user_closeable = cancellable; + }); + }); +} + +bool ImGuiFullscreen::ProgressDialog::ProgressCallbackImpl::IsCancelled() const +{ + return s_state.progress_dialog.m_cancelled.load(std::memory_order_acquire); +} + +std::unique_ptr ImGuiFullscreen::OpenModalProgressDialog(std::string title, float window_unscaled_width) +{ + return s_state.progress_dialog.GetProgressCallback(std::move(title), window_unscaled_width); +} + static float s_notification_vertical_position = 0.15f; static float s_notification_vertical_direction = 1.0f; diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index f098519c5..2b9fdacf1 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -21,6 +21,7 @@ class Image; class GPUTexture; class SmallStringBase; +class ProgressCallback; namespace ImGuiFullscreen { @@ -410,6 +411,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 OpenModalProgressDialog(std::string title, float window_unscaled_width = 500.0f); + float GetNotificationVerticalPosition(); float GetNotificationVerticalDirection(); void SetNotificationVerticalPosition(float position, float direction); @@ -459,7 +462,7 @@ public: void ClearState(); protected: - enum class State + enum class State : u8 { Inactive, ClosingTrigger, @@ -482,6 +485,7 @@ protected: std::string m_title; float m_animation_time_remaining = 0.0f; State m_state = State::Inactive; + bool m_user_closeable = true; }; // Wrapper for computing menu button bounds.