diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 33517e59b..9eafa8baa 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -44,7 +44,6 @@ #include #include -#include #include #include #include @@ -207,15 +206,6 @@ struct PostProcessingStageInfo std::vector options; }; -////////////////////////////////////////////////////////////////////////// -// Utility -////////////////////////////////////////////////////////////////////////// -static void StartAsyncOp(std::function callback, std::string name); -static void AsyncOpThreadEntryPoint(std::function callback, - FullscreenUI::ProgressCallback* progress); -static void CancelAsyncOpWithName(const std::string_view& name); -static void CancelAsyncOps(); - ////////////////////////////////////////////////////////////////////////// // Main ////////////////////////////////////////////////////////////////////////// @@ -240,11 +230,6 @@ static bool s_pause_menu_was_open = false; static bool s_was_paused_on_quick_menu_open = false; static bool s_about_window_open = false; -// async operations (e.g. cover downloads) -using AsyncOpEntry = std::pair>; -static std::mutex s_async_op_mutex; -static std::deque s_async_ops; - ////////////////////////////////////////////////////////////////////////// // Resources ////////////////////////////////////////////////////////////////////////// @@ -454,7 +439,6 @@ static bool s_save_state_selector_resuming = false; // Game List ////////////////////////////////////////////////////////////////////////// static void DrawGameListWindow(); -static void DrawCoverDownloaderWindow(); static void DrawGameList(const ImVec2& heading_size); static void DrawGameGrid(const ImVec2& heading_size); static void HandleGameListActivate(const GameList::Entry* entry); @@ -490,77 +474,6 @@ void FullscreenUI::TimeToPrintableString(SmallStringBase* str, time_t t) str->assign(buf); } -void FullscreenUI::StartAsyncOp(std::function callback, std::string name) -{ - CancelAsyncOpWithName(name); - - std::unique_lock lock(s_async_op_mutex); - std::unique_ptr progress( - std::make_unique(std::move(name))); - std::thread thread(AsyncOpThreadEntryPoint, std::move(callback), progress.get()); - s_async_ops.emplace_back(std::move(thread), std::move(progress)); -} - -void FullscreenUI::CancelAsyncOpWithName(const std::string_view& name) -{ - std::unique_lock lock(s_async_op_mutex); - for (auto iter = s_async_ops.begin(); iter != s_async_ops.end(); ++iter) - { - if (name != iter->second->GetName()) - continue; - - // move the thread out so it doesn't detach itself, then join - std::unique_ptr progress(std::move(iter->second)); - std::thread thread(std::move(iter->first)); - progress->SetCancelled(); - s_async_ops.erase(iter); - lock.unlock(); - if (thread.joinable()) - thread.join(); - lock.lock(); - break; - } -} - -void FullscreenUI::CancelAsyncOps() -{ - std::unique_lock lock(s_async_op_mutex); - while (!s_async_ops.empty()) - { - auto iter = s_async_ops.begin(); - - // move the thread out so it doesn't detach itself, then join - std::unique_ptr progress(std::move(iter->second)); - std::thread thread(std::move(iter->first)); - progress->SetCancelled(); - s_async_ops.erase(iter); - lock.unlock(); - if (thread.joinable()) - thread.join(); - lock.lock(); - } -} - -void FullscreenUI::AsyncOpThreadEntryPoint(std::function callback, - FullscreenUI::ProgressCallback* progress) -{ - Threading::SetNameOfCurrentThread(TinyString::from_format("{} Async Op", progress->GetName()).c_str()); - - callback(progress); - - // if we were removed from the list, it means we got cancelled, and the main thread is blocking - std::unique_lock lock(s_async_op_mutex); - for (auto iter = s_async_ops.begin(); iter != s_async_ops.end(); ++iter) - { - if (iter->second.get() == progress) - { - iter->first.detach(); - s_async_ops.erase(iter); - break; - } - } -} - ////////////////////////////////////////////////////////////////////////// // Main ////////////////////////////////////////////////////////////////////////// @@ -748,7 +661,6 @@ void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu) void FullscreenUI::Shutdown() { Achievements::ClearUIState(); - CancelAsyncOps(); CloseSaveStateSelector(); s_cover_image_map.clear(); s_game_list_sorted_entries = {}; @@ -1453,8 +1365,7 @@ void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, InputBindingIn { BeginInputBinding(bsi, type, section, name, display_name); } - else if (ImGui::IsItemClicked(ImGuiMouseButton_Right) || - ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false)) + else if (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false)) { bsi->DeleteValue(section, name); SetSettingsChanged(bsi); @@ -3997,9 +3908,10 @@ void FullscreenUI::DrawDisplaySettingsPage() FSUI_CSTR("Disables dithering and uses the full 8 bits per channel of color information."), "GPU", "TrueColor", true, is_hardware); - DrawToggleSetting(bsi, FSUI_CSTR("True Color Debanding"), - FSUI_CSTR("Applies modern dithering techniques to further smooth out gradients when true color is enabled."), "GPU", - "Debanding", false, is_hardware && bsi->GetBoolValue("GPU", "TrueColor", false)); + DrawToggleSetting( + bsi, FSUI_CSTR("True Color Debanding"), + FSUI_CSTR("Applies modern dithering techniques to further smooth out gradients when true color is enabled."), "GPU", + "Debanding", false, is_hardware && bsi->GetBoolValue("GPU", "TrueColor", false)); DrawToggleSetting(bsi, FSUI_CSTR("Widescreen Hack"), FSUI_CSTR("Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."), @@ -5485,8 +5397,8 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) closed = true; } - if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || - ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false))) + if (hovered && + (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false))) { s_save_state_selector_submenu_index = static_cast(i); } @@ -5888,8 +5800,8 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (hovered) selected_entry = entry; - if (selected_entry && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || - ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false))) + if (selected_entry && + (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false))) { HandleGameListOptions(selected_entry); } @@ -6106,8 +6018,8 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) if (pressed) HandleGameListActivate(entry); - if (hovered && (ImGui::IsItemClicked(ImGuiMouseButton_Right) || - ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false))) + if (hovered && + (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsKeyPressed(ImGuiKey_NavGamepadInput, false))) { HandleGameListOptions(entry); } @@ -6314,7 +6226,9 @@ void FullscreenUI::DrawGameListSettingsPage(const ImVec2& heading_size) DrawFolderSetting(bsi, FSUI_ICONSTR(ICON_FA_FOLDER, "Covers Directory"), "Folders", "Covers", EmuFolders::Covers); if (MenuButton(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Download Covers"), FSUI_CSTR("Downloads covers from a user-specified URL template."))) - ImGui::OpenPopup("Download Covers"); + { + Host::OnCoverDownloaderOpenRequested(); + } } MenuHeading("Operations"); @@ -6329,91 +6243,9 @@ void FullscreenUI::DrawGameListSettingsPage(const ImVec2& heading_size) EndMenuButtons(); - DrawCoverDownloaderWindow(); EndFullscreenWindow(); } -void FullscreenUI::DrawCoverDownloaderWindow() -{ - ImGui::SetNextWindowSize(LayoutScale(1000.0f, 0.0f)); - ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); - ImGui::PushFont(g_large_font); - - bool is_open = true; - if (ImGui::BeginPopupModal("Download Covers", &is_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) - { - ImGui::TextWrapped( - "%s", - FSUI_CSTR("DuckStation can automatically download covers for games which do not currently have a cover set. We " - "do not host any cover images, the user must provide their own source for images.")); - ImGui::NewLine(); - ImGui::TextWrapped("%s", - FSUI_CSTR("In the form below, specify the URLs to download covers from, with one template URL " - "per line. The following variables are available:")); - ImGui::NewLine(); - ImGui::TextWrapped("%s", FSUI_CSTR("${title}: Title of the game.\n${filetitle}: Name component of the game's " - "filename.\n${serial}: Serial of the game.")); - ImGui::NewLine(); - ImGui::TextWrapped("%s", FSUI_CSTR("Example: https://www.example-not-a-real-domain.com/covers/${serial}.jpg")); - ImGui::NewLine(); - - BeginMenuButtons(); - - static char template_urls[512]; - ImGui::InputTextMultiline("##templates", template_urls, sizeof(template_urls), - ImVec2(ImGui::GetCurrentWindow()->WorkRect.GetWidth(), LayoutScale(175.0f))); - - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(5.0f)); - - static bool use_serial_names; - ImGui::PushFont(g_medium_font); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(2.0f, 2.0f)); - ImGui::Checkbox(FSUI_CSTR("Use Serial File Names"), &use_serial_names); - ImGui::PopStyleVar(1); - ImGui::PopFont(); - - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); - - const bool download_enabled = (std::strlen(template_urls) > 0); - - if (ActiveButton(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Start Download"), false, download_enabled)) - { - StartAsyncOp( - [urls = StringUtil::SplitNewString(template_urls, '\n'), - use_serial_names = use_serial_names](::ProgressCallback* progress) { - GameList::DownloadCovers(urls, use_serial_names, progress, - [](const GameList::Entry* entry, std::string save_path) { - // cache the cover path on our side once it's saved - Host::RunOnCPUThread([path = entry->path, save_path = std::move(save_path)]() { - s_cover_image_map[std::move(path)] = std::move(save_path); - }); - }); - }, - "Download Covers"); - std::memset(template_urls, 0, sizeof(template_urls)); - use_serial_names = false; - ImGui::CloseCurrentPopup(); - } - - if (ActiveButton(FSUI_ICONSTR(ICON_FA_TIMES, "Cancel"), false)) - { - std::memset(template_urls, 0, sizeof(template_urls)); - use_serial_names = false; - ImGui::CloseCurrentPopup(); - } - - EndMenuButtons(); - - ImGui::EndPopup(); - } - - ImGui::PopFont(); - ImGui::PopStyleVar(2); -} - void FullscreenUI::SwitchToGameList() { s_current_main_window = MainWindowType::GameList; @@ -6609,118 +6441,6 @@ bool FullscreenUI::IsLeaderboardsWindowOpen() return (s_current_main_window == MainWindowType::Leaderboards); } -FullscreenUI::ProgressCallback::ProgressCallback(std::string name) : BaseProgressCallback(), m_name(std::move(name)) -{ - ImGuiFullscreen::OpenBackgroundProgressDialog(m_name.c_str(), "", 0, 100, 0); -} - -FullscreenUI::ProgressCallback::~ProgressCallback() -{ - ImGuiFullscreen::CloseBackgroundProgressDialog(m_name.c_str()); -} - -void FullscreenUI::ProgressCallback::PushState() -{ - BaseProgressCallback::PushState(); -} - -void FullscreenUI::ProgressCallback::PopState() -{ - BaseProgressCallback::PopState(); - Redraw(true); -} - -void FullscreenUI::ProgressCallback::SetCancellable(bool cancellable) -{ - BaseProgressCallback::SetCancellable(cancellable); - Redraw(true); -} - -void FullscreenUI::ProgressCallback::SetTitle(const char* title) -{ - // todo? -} - -void FullscreenUI::ProgressCallback::SetStatusText(const char* text) -{ - BaseProgressCallback::SetStatusText(text); - Redraw(true); -} - -void FullscreenUI::ProgressCallback::SetProgressRange(u32 range) -{ - u32 last_range = m_progress_range; - - BaseProgressCallback::SetProgressRange(range); - - if (m_progress_range != last_range) - Redraw(false); -} - -void FullscreenUI::ProgressCallback::SetProgressValue(u32 value) -{ - u32 lastValue = m_progress_value; - - BaseProgressCallback::SetProgressValue(value); - - if (m_progress_value != lastValue) - Redraw(false); -} - -void FullscreenUI::ProgressCallback::Redraw(bool force) -{ - const int percent = - static_cast((static_cast(m_progress_value) / static_cast(m_progress_range)) * 100.0f); - if (percent == m_last_progress_percent && !force) - return; - - m_last_progress_percent = percent; - ImGuiFullscreen::UpdateBackgroundProgressDialog(m_name.c_str(), m_status_text, 0, 100, percent); -} - -void FullscreenUI::ProgressCallback::DisplayError(const char* message) -{ - Log_ErrorPrint(message); - Host::ReportErrorAsync("Error", message); -} - -void FullscreenUI::ProgressCallback::DisplayWarning(const char* message) -{ - Log_WarningPrint(message); -} - -void FullscreenUI::ProgressCallback::DisplayInformation(const char* message) -{ - Log_InfoPrint(message); -} - -void FullscreenUI::ProgressCallback::DisplayDebugMessage(const char* message) -{ - Log_DebugPrint(message); -} - -void FullscreenUI::ProgressCallback::ModalError(const char* message) -{ - Log_ErrorPrint(message); - Host::ReportErrorAsync("Error", message); -} - -bool FullscreenUI::ProgressCallback::ModalConfirmation(const char* message) -{ - return false; -} - -void FullscreenUI::ProgressCallback::ModalInformation(const char* message) -{ - Log_InfoPrint(message); -} - -void FullscreenUI::ProgressCallback::SetCancelled() -{ - if (m_cancellable) - m_cancelled = true; -} - #endif // __ANDROID__ ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -6732,7 +6452,6 @@ void FullscreenUI::ProgressCallback::SetCancelled() #if 0 // TRANSLATION-STRING-AREA-BEGIN -TRANSLATE_NOOP("FullscreenUI", "${title}: Title of the game.\n${filetitle}: Name component of the game's filename.\n${serial}: Serial of the game."); TRANSLATE_NOOP("FullscreenUI", "-"); TRANSLATE_NOOP("FullscreenUI", "1 Frame"); TRANSLATE_NOOP("FullscreenUI", "10 Frames"); @@ -6815,6 +6534,7 @@ TRANSLATE_NOOP("FullscreenUI", "Advanced Settings"); TRANSLATE_NOOP("FullscreenUI", "All Time: {}"); TRANSLATE_NOOP("FullscreenUI", "Allow Booting Without SBI File"); TRANSLATE_NOOP("FullscreenUI", "Allows loading protected games without subchannel information."); +TRANSLATE_NOOP("FullscreenUI", "Applies modern dithering techniques to further smooth out gradients when true color is enabled."); TRANSLATE_NOOP("FullscreenUI", "Apply Image Patches"); TRANSLATE_NOOP("FullscreenUI", "Apply Per-Game Settings"); TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to clear the current post-processing chain? All configuration will be lost."); @@ -6937,7 +6657,6 @@ TRANSLATE_NOOP("FullscreenUI", "Downsamples the rendered image prior to displayi TRANSLATE_NOOP("FullscreenUI", "Downsampling"); TRANSLATE_NOOP("FullscreenUI", "Downsampling Display Scale"); TRANSLATE_NOOP("FullscreenUI", "Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title)"); -TRANSLATE_NOOP("FullscreenUI", "DuckStation can automatically download covers for games which do not currently have a cover set. We do not host any cover images, the user must provide their own source for images."); TRANSLATE_NOOP("FullscreenUI", "DuckStation is a free and open-source simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability."); TRANSLATE_NOOP("FullscreenUI", "Dump Replaceable VRAM Writes"); TRANSLATE_NOOP("FullscreenUI", "Emulation Settings"); @@ -6972,7 +6691,6 @@ TRANSLATE_NOOP("FullscreenUI", "Enhancements"); TRANSLATE_NOOP("FullscreenUI", "Ensures every frame generated is displayed for optimal pacing. Disable if you are having speed or sound issues."); TRANSLATE_NOOP("FullscreenUI", "Enter the name of the input profile you wish to create."); TRANSLATE_NOOP("FullscreenUI", "Enter the name of the memory card you wish to create."); -TRANSLATE_NOOP("FullscreenUI", "Example: https://www.example-not-a-real-domain.com/covers/${serial}.jpg"); TRANSLATE_NOOP("FullscreenUI", "Execution Mode"); TRANSLATE_NOOP("FullscreenUI", "Exit"); TRANSLATE_NOOP("FullscreenUI", "Exit And Save State"); @@ -7030,7 +6748,6 @@ TRANSLATE_NOOP("FullscreenUI", "How many saves will be kept for rewinding. Highe TRANSLATE_NOOP("FullscreenUI", "How often a rewind state will be created. Higher frequencies have greater system requirements."); TRANSLATE_NOOP("FullscreenUI", "Identifies any new files added to the game directories."); TRANSLATE_NOOP("FullscreenUI", "If not enabled, the current post processing chain will be ignored."); -TRANSLATE_NOOP("FullscreenUI", "In the form below, specify the URLs to download covers from, with one template URL per line. The following variables are available:"); TRANSLATE_NOOP("FullscreenUI", "Increase Timer Resolution"); TRANSLATE_NOOP("FullscreenUI", "Increases the field of view from 4:3 to the chosen display aspect ratio in 3D games."); TRANSLATE_NOOP("FullscreenUI", "Increases the precision of polygon culling, reducing the number of holes in geometry."); @@ -7264,7 +6981,6 @@ TRANSLATE_NOOP("FullscreenUI", "Speeds up CD-ROM reads by the specified factor. TRANSLATE_NOOP("FullscreenUI", "Speeds up CD-ROM seeks by the specified factor. May improve loading speeds in some games, and break others."); TRANSLATE_NOOP("FullscreenUI", "Stage {}: {}"); TRANSLATE_NOOP("FullscreenUI", "Start BIOS"); -TRANSLATE_NOOP("FullscreenUI", "Start Download"); TRANSLATE_NOOP("FullscreenUI", "Start File"); TRANSLATE_NOOP("FullscreenUI", "Start Fullscreen"); TRANSLATE_NOOP("FullscreenUI", "Start the console without any disc inserted."); @@ -7298,6 +7014,7 @@ TRANSLATE_NOOP("FullscreenUI", "Title"); TRANSLATE_NOOP("FullscreenUI", "Toggle Analog"); TRANSLATE_NOOP("FullscreenUI", "Toggle Fast Forward"); TRANSLATE_NOOP("FullscreenUI", "Toggle every %d frames"); +TRANSLATE_NOOP("FullscreenUI", "True Color Debanding"); TRANSLATE_NOOP("FullscreenUI", "True Color Rendering"); TRANSLATE_NOOP("FullscreenUI", "Turbo Speed"); TRANSLATE_NOOP("FullscreenUI", "Type"); @@ -7312,7 +7029,6 @@ TRANSLATE_NOOP("FullscreenUI", "Use Blit Swap Chain"); TRANSLATE_NOOP("FullscreenUI", "Use Debug GPU Device"); TRANSLATE_NOOP("FullscreenUI", "Use Global Setting"); TRANSLATE_NOOP("FullscreenUI", "Use Light Theme"); -TRANSLATE_NOOP("FullscreenUI", "Use Serial File Names"); TRANSLATE_NOOP("FullscreenUI", "Use Single Card For Multi-Disc Games"); TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer For Readbacks"); TRANSLATE_NOOP("FullscreenUI", "Username: {}"); diff --git a/src/core/fullscreen_ui.h b/src/core/fullscreen_ui.h index 40afc70cf..99e54894c 100644 --- a/src/core/fullscreen_ui.h +++ b/src/core/fullscreen_ui.h @@ -41,38 +41,9 @@ void Render(); void InvalidateCoverCache(); void TimeToPrintableString(SmallStringBase* str, time_t t); -class ProgressCallback final : public BaseProgressCallback -{ -public: - ProgressCallback(std::string name); - ~ProgressCallback() override; - - ALWAYS_INLINE const std::string& GetName() const { return m_name; } - - void PushState() override; - void PopState() override; - - void SetCancellable(bool cancellable) override; - void SetTitle(const char* title) override; - void SetStatusText(const char* text) override; - void SetProgressRange(u32 range) override; - void SetProgressValue(u32 value) override; - - void DisplayError(const char* message) override; - void DisplayWarning(const char* message) override; - void DisplayInformation(const char* message) override; - void DisplayDebugMessage(const char* message) override; - - void ModalError(const char* message) override; - bool ModalConfirmation(const char* message) override; - void ModalInformation(const char* message) override; - - void SetCancelled(); - -private: - void Redraw(bool force); - - std::string m_name; - int m_last_progress_percent = -1; -}; } // namespace FullscreenUI + +// Host UI triggers from Big Picture mode. +namespace Host { +void OnCoverDownloaderOpenRequested(); +} // namespace Host diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index 284593ffe..714845ca3 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -17,6 +17,7 @@ #include "core/system.h" #include "util/gpu_device.h" +#include "util/imgui_fullscreen.h" #include "util/imgui_manager.h" #include "util/ini_settings_interface.h" #include "util/input_manager.h" @@ -59,6 +60,44 @@ std::unique_ptr g_nogui_window; // Local function declarations ////////////////////////////////////////////////////////////////////////// namespace NoGUIHost { + +namespace { +class AsyncOpProgressCallback final : public BaseProgressCallback +{ +public: + AsyncOpProgressCallback(std::string name); + ~AsyncOpProgressCallback() override; + + ALWAYS_INLINE const std::string& GetName() const { return m_name; } + + void PushState() override; + void PopState() override; + + void SetCancellable(bool cancellable) override; + void SetTitle(const char* title) override; + void SetStatusText(const char* text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + + void DisplayError(const char* message) override; + void DisplayWarning(const char* message) override; + void DisplayInformation(const char* message) override; + void DisplayDebugMessage(const char* message) override; + + void ModalError(const char* message) override; + bool ModalConfirmation(const char* message) override; + void ModalInformation(const char* message) override; + + void SetCancelled(); + +private: + void Redraw(bool force); + + std::string m_name; + int m_last_progress_percent = -1; +}; +} // namespace + /// Starts the virtual machine. static void StartSystem(SystemBootParameters params); @@ -88,7 +127,6 @@ static void UpdateWindowTitle(const std::string& game_title); static void CancelAsyncOp(); static void StartAsyncOp(std::function callback); static void AsyncOpThreadEntryPoint(std::function callback); -} // namespace NoGUIHost ////////////////////////////////////////////////////////////////////////// // Local variable declarations @@ -109,7 +147,8 @@ static u32 s_blocking_cpu_events_pending = 0; // TODO: Token system would work b static std::mutex s_async_op_mutex; static std::thread s_async_op_thread; -static FullscreenUI::ProgressCallback* s_async_op_progress = nullptr; +static AsyncOpProgressCallback* s_async_op_progress = nullptr; +} // namespace NoGUIHost ////////////////////////////////////////////////////////////////////////// // Initialization/Shutdown @@ -694,10 +733,10 @@ std::optional Host::AcquireRenderWindow(bool recreate_window) } if (res) wi = g_nogui_window->GetPlatformWindowInfo(); - s_platform_window_updated.Post(); + NoGUIHost::s_platform_window_updated.Post(); }); - s_platform_window_updated.Wait(); + NoGUIHost::s_platform_window_updated.Wait(); if (!wi.has_value()) { @@ -719,14 +758,14 @@ void Host::ReleaseRenderWindow() // Need to block here, otherwise the recreation message associates with the old window. g_nogui_window->ExecuteInMessageLoop([]() { g_nogui_window->DestroyPlatformWindow(); - s_platform_window_updated.Post(); + NoGUIHost::s_platform_window_updated.Post(); }); - s_platform_window_updated.Wait(); + NoGUIHost::s_platform_window_updated.Wait(); } void Host::OnSystemStarting() { - s_was_paused_by_focus_loss = false; + NoGUIHost::s_was_paused_by_focus_loss = false; } void Host::OnSystemStarted() @@ -800,6 +839,11 @@ void Host::OnAchievementsHardcoreModeChanged(bool enabled) // noop } +void Host::OnCoverDownloaderOpenRequested() +{ + // noop +} + void Host::SetMouseMode(bool relative, bool hide_cursor) { // noop @@ -854,11 +898,11 @@ void NoGUIHost::UpdateWindowTitle(const std::string& game_title) void Host::RunOnCPUThread(std::function function, bool block /* = false */) { - std::unique_lock lock(s_cpu_thread_events_mutex); - s_cpu_thread_events.emplace_back(std::move(function), block); - s_cpu_thread_event_posted.notify_one(); + std::unique_lock lock(NoGUIHost::s_cpu_thread_events_mutex); + NoGUIHost::s_cpu_thread_events.emplace_back(std::move(function), block); + NoGUIHost::s_cpu_thread_event_posted.notify_one(); if (block) - s_cpu_thread_event_done.wait(lock, []() { return s_blocking_cpu_events_pending == 0; }); + NoGUIHost::s_cpu_thread_event_done.wait(lock, []() { return NoGUIHost::s_blocking_cpu_events_pending == 0; }); } void NoGUIHost::StartAsyncOp(std::function callback) @@ -884,7 +928,7 @@ void NoGUIHost::AsyncOpThreadEntryPoint(std::function c { Threading::SetNameOfCurrentThread("Async Op"); - FullscreenUI::ProgressCallback fs_callback("async_op"); + AsyncOpProgressCallback fs_callback("async_op"); std::unique_lock lock(s_async_op_mutex); s_async_op_progress = &fs_callback; @@ -908,15 +952,15 @@ void Host::CancelGameListRefresh() bool Host::IsFullscreen() { - return s_is_fullscreen; + return NoGUIHost::s_is_fullscreen; } void Host::SetFullscreen(bool enabled) { - if (s_is_fullscreen == enabled) + if (NoGUIHost::s_is_fullscreen == enabled) return; - s_is_fullscreen = enabled; + NoGUIHost::s_is_fullscreen = enabled; g_nogui_window->SetFullscreen(enabled); } @@ -933,7 +977,7 @@ void Host::RequestExit(bool allow_confirm) } // clear the running flag, this'll break out of the main CPU loop once the VM is shutdown. - s_running.store(false, std::memory_order_release); + NoGUIHost::s_running.store(false, std::memory_order_release); } void Host::RequestSystemShutdown(bool allow_confirm, bool save_state) @@ -1230,6 +1274,119 @@ bool NoGUIHost::ParseCommandLineParametersAndInitializeConfig(int argc, char* ar return true; } +NoGUIHost::AsyncOpProgressCallback::AsyncOpProgressCallback(std::string name) + : BaseProgressCallback(), m_name(std::move(name)) +{ + ImGuiFullscreen::OpenBackgroundProgressDialog(m_name.c_str(), "", 0, 100, 0); +} + +NoGUIHost::AsyncOpProgressCallback::~AsyncOpProgressCallback() +{ + ImGuiFullscreen::CloseBackgroundProgressDialog(m_name.c_str()); +} + +void NoGUIHost::AsyncOpProgressCallback::PushState() +{ + BaseProgressCallback::PushState(); +} + +void NoGUIHost::AsyncOpProgressCallback::PopState() +{ + BaseProgressCallback::PopState(); + Redraw(true); +} + +void NoGUIHost::AsyncOpProgressCallback::SetCancellable(bool cancellable) +{ + BaseProgressCallback::SetCancellable(cancellable); + Redraw(true); +} + +void NoGUIHost::AsyncOpProgressCallback::SetTitle(const char* title) +{ + // todo? +} + +void NoGUIHost::AsyncOpProgressCallback::SetStatusText(const char* text) +{ + BaseProgressCallback::SetStatusText(text); + Redraw(true); +} + +void NoGUIHost::AsyncOpProgressCallback::SetProgressRange(u32 range) +{ + u32 last_range = m_progress_range; + + BaseProgressCallback::SetProgressRange(range); + + if (m_progress_range != last_range) + Redraw(false); +} + +void NoGUIHost::AsyncOpProgressCallback::SetProgressValue(u32 value) +{ + u32 lastValue = m_progress_value; + + BaseProgressCallback::SetProgressValue(value); + + if (m_progress_value != lastValue) + Redraw(false); +} + +void NoGUIHost::AsyncOpProgressCallback::Redraw(bool force) +{ + const int percent = + static_cast((static_cast(m_progress_value) / static_cast(m_progress_range)) * 100.0f); + if (percent == m_last_progress_percent && !force) + return; + + m_last_progress_percent = percent; + ImGuiFullscreen::UpdateBackgroundProgressDialog(m_name.c_str(), m_status_text, 0, 100, percent); +} + +void NoGUIHost::AsyncOpProgressCallback::DisplayError(const char* message) +{ + Log_ErrorPrint(message); + Host::ReportErrorAsync("Error", message); +} + +void NoGUIHost::AsyncOpProgressCallback::DisplayWarning(const char* message) +{ + Log_WarningPrint(message); +} + +void NoGUIHost::AsyncOpProgressCallback::DisplayInformation(const char* message) +{ + Log_InfoPrint(message); +} + +void NoGUIHost::AsyncOpProgressCallback::DisplayDebugMessage(const char* message) +{ + Log_DebugPrint(message); +} + +void NoGUIHost::AsyncOpProgressCallback::ModalError(const char* message) +{ + Log_ErrorPrint(message); + Host::ReportErrorAsync("Error", message); +} + +bool NoGUIHost::AsyncOpProgressCallback::ModalConfirmation(const char* message) +{ + return false; +} + +void NoGUIHost::AsyncOpProgressCallback::ModalInformation(const char* message) +{ + Log_InfoPrint(message); +} + +void NoGUIHost::AsyncOpProgressCallback::SetCancelled() +{ + if (m_cancellable) + m_cancelled = true; +} + int main(int argc, char* argv[]) { CrashHandler::Install(); @@ -1257,7 +1414,7 @@ int main(int argc, char* argv[]) // Ensure log is flushed. Log::SetFileOutputParams(false, nullptr); - s_base_settings_interface.reset(); + NoGUIHost::s_base_settings_interface.reset(); g_nogui_window.reset(); return EXIT_SUCCESS; } diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index c7d31c943..d0a606979 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2063,6 +2063,7 @@ void MainWindow::connectSignals() connect(g_emu_thread, &EmuThread::achievementsLoginSucceeded, this, &MainWindow::onAchievementsLoginSucceeded); connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this, &MainWindow::onAchievementsChallengeModeChanged); + connect(g_emu_thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered); // These need to be queued connections to stop crashing due to menus opening/closing and switching focus. connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress); @@ -2790,7 +2791,9 @@ void MainWindow::onToolsMemoryCardEditorTriggered() void MainWindow::onToolsCoverDownloaderTriggered() { - CoverDownloadDialog dlg(this); + // This can be invoked via big picture, so exit fullscreen. + SystemLock lock(pauseAndLockSystem()); + CoverDownloadDialog dlg(lock.getDialogParent()); connect(&dlg, &CoverDownloadDialog::coverRefreshRequested, m_game_list_widget, &GameListWidget::refreshGridCovers); dlg.exec(); } diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 9c6f3d974..37d1f0fd0 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1418,6 +1418,11 @@ void Host::OnAchievementsHardcoreModeChanged(bool enabled) emit g_emu_thread->achievementsChallengeModeChanged(enabled); } +void Host::OnCoverDownloaderOpenRequested() +{ + emit g_emu_thread->onCoverDownloaderOpenRequested(); +} + void EmuThread::doBackgroundControllerPoll() { System::Internal::IdlePollUpdate(); diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 336187d34..97d768158 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -150,6 +150,9 @@ Q_SIGNALS: void achievementsChallengeModeChanged(bool enabled); void cheatEnabled(quint32 index, bool enabled); + /// Big Picture UI requests. + void onCoverDownloaderOpenRequested(); + public Q_SLOTS: void setDefaultSettings(bool system = true, bool controller = true); void applySettings(bool display_osd_messages = false); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 3b8df9b91..a20deec20 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -1,7 +1,8 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "core/achievements.h" +#include "core/fullscreen_ui.h" #include "core/game_list.h" #include "core/gpu.h" #include "core/host.h" @@ -368,6 +369,11 @@ void Host::OnAchievementsHardcoreModeChanged(bool enabled) // noop } +void Host::OnCoverDownloaderOpenRequested() +{ + // noop +} + std::optional InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str) { return std::nullopt;