mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-10-12 02:08:45 +00:00
FullscreenUI: Implement cover download support
Signed-off-by: SternXD <[email protected]>
This commit is contained in:
+378
-11
@@ -379,7 +379,7 @@ bool FullscreenUI::HasActiveWindow()
|
||||
|
||||
bool FullscreenUI::AreAnyDialogsOpen()
|
||||
{
|
||||
return (s_save_state_selector_open || s_about_window_open ||
|
||||
return (s_save_state_selector_open || s_about_window_open || s_cover_downloader_open ||
|
||||
s_input_binding_type != InputBindingInfo::Type::Unknown || ImGuiFullscreen::IsChoiceDialogOpen() ||
|
||||
ImGuiFullscreen::IsFileSelectorOpen());
|
||||
}
|
||||
@@ -532,6 +532,7 @@ void FullscreenUI::Shutdown(bool clear_state)
|
||||
{
|
||||
CancelAllHddOperations();
|
||||
CloseSaveStateSelector();
|
||||
CloseCoverDownloaderWindow();
|
||||
s_cover_image_map.clear();
|
||||
s_game_list_sorted_entries = {};
|
||||
s_game_list_directories_cache = {};
|
||||
@@ -649,6 +650,9 @@ void FullscreenUI::Render()
|
||||
if (s_about_window_open)
|
||||
DrawAboutWindow();
|
||||
|
||||
if (s_cover_downloader_open)
|
||||
DrawCoverDownloaderWindow();
|
||||
|
||||
if (s_achievements_login_open || ImGui::IsPopupOpen("RetroAchievements"))
|
||||
DrawAchievementsLoginWindow();
|
||||
|
||||
@@ -2611,6 +2615,10 @@ void FullscreenUI::DrawGameListWindow()
|
||||
s_current_main_window = MainWindowType::GameListSettings;
|
||||
QueueResetFocus(FocusResetType::WindowChanged);
|
||||
}
|
||||
else if (ImGui::IsKeyPressed(ImGuiKey_GamepadBack, false) || ImGui::IsKeyPressed(ImGuiKey_F4, false))
|
||||
{
|
||||
OpenCoverDownloaderWindow();
|
||||
}
|
||||
|
||||
switch (s_game_list_view)
|
||||
{
|
||||
@@ -2643,6 +2651,7 @@ void FullscreenUI::DrawGameListWindow()
|
||||
const auto glyphs = GetGamepadGlyphs();
|
||||
SetFullscreenFooterText(std::array{
|
||||
std::make_pair(glyphs.dpad, FSUI_VSTR("Select Game")),
|
||||
std::make_pair(glyphs.select, FSUI_VSTR("Cover Downloader")),
|
||||
std::make_pair(glyphs.start, FSUI_VSTR("Settings")),
|
||||
std::make_pair(swapNorthWest ? glyphs.west : glyphs.north, FSUI_VSTR("Change View")),
|
||||
std::make_pair(swapNorthWest ? glyphs.north : glyphs.west, FSUI_VSTR("Launch Options")),
|
||||
@@ -2657,6 +2666,7 @@ void FullscreenUI::DrawGameListWindow()
|
||||
std::make_pair(ICON_PF_F1, FSUI_VSTR("Change View")),
|
||||
std::make_pair(ICON_PF_F2, FSUI_VSTR("Settings")),
|
||||
std::make_pair(ICON_PF_F3, FSUI_VSTR("Launch Options")),
|
||||
std::make_pair(ICON_PF_F4, FSUI_VSTR("Cover Downloader")),
|
||||
std::make_pair(ICON_PF_ENTER, FSUI_VSTR("Start Game")),
|
||||
std::make_pair(ICON_PF_ESC, FSUI_VSTR("Back")),
|
||||
});
|
||||
@@ -3228,16 +3238,6 @@ void FullscreenUI::DrawGameListSettingsWindow()
|
||||
"FullscreenUIShowGameGridTitles", true);
|
||||
}
|
||||
|
||||
MenuHeading(FSUI_CSTR("Cover Settings"));
|
||||
{
|
||||
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.")))
|
||||
{
|
||||
Host::OnCoverDownloaderOpenRequested();
|
||||
}
|
||||
}
|
||||
|
||||
MenuHeading(FSUI_CSTR("Operations"));
|
||||
{
|
||||
if (MenuButton(
|
||||
@@ -3482,6 +3482,373 @@ void FullscreenUI::DrawAboutWindow()
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
void FullscreenUI::OpenCoverDownloaderWindow()
|
||||
{
|
||||
s_cover_downloader_open = true;
|
||||
s_cover_downloader_urls_buffer.fill('\0');
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_use_title_filenames = false;
|
||||
s_cover_downloader_downloading = false;
|
||||
s_cover_downloader_has_error = false;
|
||||
}
|
||||
QueueResetFocus(FocusResetType::PopupOpened);
|
||||
}
|
||||
|
||||
void FullscreenUI::CloseCoverDownloaderWindow()
|
||||
{
|
||||
if (s_cover_downloader_thread)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_downloading = false;
|
||||
}
|
||||
if (s_cover_downloader_thread->joinable())
|
||||
{
|
||||
s_cover_downloader_thread->join();
|
||||
}
|
||||
s_cover_downloader_thread.reset();
|
||||
}
|
||||
|
||||
s_cover_downloader_open = false;
|
||||
s_cover_downloader_urls_buffer.fill('\0');
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_use_title_filenames = false;
|
||||
s_cover_downloader_has_error = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FullscreenUI::CoverDownloaderThreadFunc(const std::vector<std::string>& urls)
|
||||
{
|
||||
class CoverDownloaderProgressCallback : public ProgressCallback
|
||||
{
|
||||
public:
|
||||
CoverDownloaderProgressCallback() = default;
|
||||
~CoverDownloaderProgressCallback() = default;
|
||||
|
||||
void PushState() override {}
|
||||
void PopState() override {}
|
||||
|
||||
bool IsCancelled() const override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
return !s_cover_downloader_downloading;
|
||||
}
|
||||
|
||||
bool IsCancellable() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetCancellable(bool cancellable) override
|
||||
{
|
||||
}
|
||||
|
||||
void SetTitle(const char* title) override
|
||||
{
|
||||
}
|
||||
|
||||
void SetStatusText(const char* text) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_status = text;
|
||||
}
|
||||
|
||||
void SetProgressRange(u32 range) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_progress_max = range;
|
||||
s_cover_downloader_progress_value = 0;
|
||||
}
|
||||
|
||||
void SetProgressValue(u32 value) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_progress_value = value;
|
||||
}
|
||||
|
||||
void IncrementProgressValue() override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_progress_value++;
|
||||
}
|
||||
|
||||
void SetProgressState(ProgressState state) override
|
||||
{
|
||||
}
|
||||
|
||||
void DisplayError(const char* message) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_status = fmt::format(FSUI_FSTR("Error: {}"), message);
|
||||
s_cover_downloader_has_error = true;
|
||||
}
|
||||
|
||||
void DisplayWarning(const char* message) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_status = fmt::format(FSUI_FSTR("Warning: {}"), message);
|
||||
s_cover_downloader_has_error = false;
|
||||
}
|
||||
|
||||
void DisplayInformation(const char* message) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_status = message;
|
||||
s_cover_downloader_has_error = false;
|
||||
}
|
||||
|
||||
void DisplayDebugMessage(const char* message) override
|
||||
{
|
||||
}
|
||||
|
||||
void ModalError(const char* message) override
|
||||
{
|
||||
DisplayError(message);
|
||||
}
|
||||
|
||||
bool ModalConfirmation(const char* message) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModalInformation(const char* message) override
|
||||
{
|
||||
DisplayInformation(message);
|
||||
}
|
||||
};
|
||||
|
||||
CoverDownloaderProgressCallback callback;
|
||||
bool use_title_filenames;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
use_title_filenames = s_cover_downloader_use_title_filenames;
|
||||
}
|
||||
GameList::DownloadCovers(urls, !use_title_filenames, &callback);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
if (s_cover_downloader_has_error && !s_cover_downloader_status.empty())
|
||||
{
|
||||
std::string error_message = s_cover_downloader_status;
|
||||
MTGS::RunOnGSThread([error_message]() {
|
||||
ShowToast(FSUI_STR("Download Failed"), error_message, 5.0f);
|
||||
});
|
||||
}
|
||||
// We clear the cover image cache so the newly downloaded covers are picked up
|
||||
MTGS::RunOnGSThread([]() {
|
||||
s_cover_image_map.clear();
|
||||
});
|
||||
s_cover_downloader_downloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FullscreenUI::DrawCoverDownloaderWindow()
|
||||
{
|
||||
if (!s_cover_downloader_open)
|
||||
return;
|
||||
|
||||
ImGui::SetNextWindowSize(LayoutScale(700.0f, 0.0f));
|
||||
ImGui::SetNextWindowPos(
|
||||
(ImGui::GetIO().DisplaySize - LayoutScale(0.0f, ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT)) * 0.5f,
|
||||
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::OpenPopup(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Cover Downloader"));
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||
LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
|
||||
|
||||
bool is_open = !WantsToCloseMenu();
|
||||
const u32 flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar;
|
||||
|
||||
if (ImGui::BeginPopupModal(FSUI_ICONSTR(ICON_FA_DOWNLOAD, "Cover Downloader"), &is_open, flags))
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor);
|
||||
BeginMenuButtons();
|
||||
ResetFocusHere();
|
||||
|
||||
ImGui::TextWrapped("%s", FSUI_CSTR("PCSX2 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::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(8.0f));
|
||||
ImGui::TextWrapped("%s", FSUI_CSTR("Enter one or more cover image URL templates below. Variables such as ${serial} and ${title} are supported. See the Qt Cover Downloader for more information."));
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
||||
|
||||
|
||||
// URLs input section
|
||||
bool is_downloading;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
is_downloading = s_cover_downloader_downloading;
|
||||
}
|
||||
|
||||
{
|
||||
ImGui::TextUnformatted(FSUI_CSTR("URLs:"));
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(4.0f));
|
||||
|
||||
const float text_box_height = LayoutScale(55.0f);
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(8.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(12.0f, 10.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, LayoutScale(1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.2f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.25f, 0.25f, 0.25f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
if (is_downloading)
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::InputTextMultiline("##urls", s_cover_downloader_urls_buffer.data(), s_cover_downloader_urls_buffer.size(),
|
||||
ImVec2(-1.0f, text_box_height), ImGuiInputTextFlags_AllowTabInput);
|
||||
if (is_downloading)
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::PopStyleColor(5);
|
||||
ImGui::PopStyleVar(3);
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
||||
|
||||
// Use Title File Names toggle
|
||||
{
|
||||
bool use_title;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
use_title = s_cover_downloader_use_title_filenames;
|
||||
}
|
||||
|
||||
if (ToggleButton(FSUI_ICONSTR(ICON_FA_FILE_SIGNATURE, "Use Title File Names"),
|
||||
FSUI_CSTR("Saves covers using the game's title instead of serial number."),
|
||||
&use_title, !is_downloading))
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_use_title_filenames = use_title;
|
||||
}
|
||||
}
|
||||
|
||||
// Progress display
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
|
||||
if (s_cover_downloader_downloading)
|
||||
{
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f));
|
||||
|
||||
if (!s_cover_downloader_status.empty())
|
||||
ImGui::TextUnformatted(s_cover_downloader_status.c_str());
|
||||
else
|
||||
ImGui::TextUnformatted(FSUI_CSTR("Downloading covers..."));
|
||||
|
||||
if (s_cover_downloader_progress_max > 0)
|
||||
{
|
||||
const float progress = static_cast<float>(s_cover_downloader_progress_value) /
|
||||
static_cast<float>(s_cover_downloader_progress_max);
|
||||
const int percent = static_cast<int>(std::round(progress * 100.0f));
|
||||
ImGui::ProgressBar(progress, ImVec2(-1.0f, LayoutScale(30.0f)), fmt::format("{}%", percent).c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
const float fraction = std::fmod(ImGui::GetTime(), 2.0f) * 0.5f;
|
||||
ImGui::ProgressBar(fraction, ImVec2(-1.0f, LayoutScale(30.0f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(20.0f));
|
||||
|
||||
const bool has_urls = (s_cover_downloader_urls_buffer[0] != '\0');
|
||||
bool start_enabled;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
start_enabled = !s_cover_downloader_downloading && has_urls;
|
||||
}
|
||||
|
||||
if (is_downloading)
|
||||
{
|
||||
if (ActiveButton(FSUI_ICONSTR(ICON_FA_STOP, "Stop"), false))
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
s_cover_downloader_downloading = false;
|
||||
}
|
||||
if (s_cover_downloader_thread && s_cover_downloader_thread->joinable())
|
||||
s_cover_downloader_thread->join();
|
||||
s_cover_downloader_thread.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Start"), false, start_enabled))
|
||||
{
|
||||
std::vector<std::string> urls;
|
||||
std::string urls_text(s_cover_downloader_urls_buffer.data());
|
||||
for (const auto& line : StringUtil::SplitString(urls_text, '\n', true))
|
||||
{
|
||||
if (!line.empty())
|
||||
urls.push_back(std::string(line));
|
||||
}
|
||||
|
||||
if (s_cover_downloader_thread && s_cover_downloader_thread->joinable())
|
||||
{
|
||||
s_cover_downloader_thread->join();
|
||||
s_cover_downloader_thread.reset();
|
||||
}
|
||||
|
||||
bool should_start_download = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_cover_downloader_mutex);
|
||||
if (!s_cover_downloader_downloading)
|
||||
{
|
||||
s_cover_downloader_progress_max = 0;
|
||||
s_cover_downloader_progress_value = 0;
|
||||
s_cover_downloader_status.clear();
|
||||
s_cover_downloader_has_error = false;
|
||||
s_cover_downloader_downloading = true;
|
||||
should_start_download = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_start_download)
|
||||
s_cover_downloader_thread = std::make_unique<std::thread>(CoverDownloaderThreadFunc, urls);
|
||||
}
|
||||
}
|
||||
|
||||
if (ActiveButton(FSUI_ICONSTR(ICON_FA_XMARK, "Close"), false))
|
||||
{
|
||||
is_open = false;
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
ImGui::PopStyleColor(1);
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
else
|
||||
{
|
||||
is_open = false;
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(4);
|
||||
ImGui::PopStyleVar(4);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (!is_open)
|
||||
{
|
||||
ImGui::CloseCurrentPopup();
|
||||
s_cover_downloader_open = false;
|
||||
CloseCoverDownloaderWindow();
|
||||
}
|
||||
}
|
||||
|
||||
bool FullscreenUI::OpenAchievementsWindow()
|
||||
{
|
||||
if (!VMManager::HasValidVM() || !Achievements::IsActive())
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "common/Timer.h"
|
||||
#include "Input/InputManager.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#define TR_CONTEXT "FullscreenUI"
|
||||
|
||||
template <size_t L>
|
||||
@@ -215,6 +217,10 @@ namespace FullscreenUI
|
||||
void CopyTextToClipboard(std::string title, const std::string_view text);
|
||||
void DrawAboutWindow();
|
||||
void OpenAboutWindow();
|
||||
void DrawCoverDownloaderWindow();
|
||||
void OpenCoverDownloaderWindow();
|
||||
void CloseCoverDownloaderWindow();
|
||||
void CoverDownloaderThreadFunc(const std::vector<std::string>& urls);
|
||||
void GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel);
|
||||
void ApplyLayoutSettings(const SettingsInterface* bsi = nullptr);
|
||||
|
||||
@@ -239,6 +245,18 @@ namespace FullscreenUI
|
||||
inline char s_achievements_login_password[256] = {};
|
||||
inline Achievements::LoginRequestReason s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
|
||||
|
||||
// cover downloader dialog state
|
||||
inline bool s_cover_downloader_open = false;
|
||||
inline std::array<char, 4096> s_cover_downloader_urls_buffer = {};
|
||||
inline bool s_cover_downloader_use_title_filenames = false;
|
||||
inline bool s_cover_downloader_downloading = false;
|
||||
inline std::unique_ptr<std::thread> s_cover_downloader_thread;
|
||||
inline std::mutex s_cover_downloader_mutex;
|
||||
inline std::string s_cover_downloader_status;
|
||||
inline bool s_cover_downloader_has_error = false;
|
||||
inline s32 s_cover_downloader_progress_max = 0;
|
||||
inline s32 s_cover_downloader_progress_value = 0;
|
||||
|
||||
// local copies of the currently-running game
|
||||
inline std::string s_current_game_title;
|
||||
inline std::string s_current_game_subtitle;
|
||||
|
||||
Reference in New Issue
Block a user