From 7f16f122026a950bcd23236efc4766194e393501 Mon Sep 17 00:00:00 2001 From: swurl Date: Thu, 29 May 2025 08:20:11 +0000 Subject: [PATCH] Add game list refresh button (#126) nice race condition Signed-off-by: swurl Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/126 Co-authored-by: swurl Co-committed-by: swurl --- dist/qt_themes/default/style.qss | 12 +++ dist/qt_themes/default_dark/style.qss | 12 +++ dist/qt_themes/qdarkstyle/style.qss | 13 +++ .../qdarkstyle_midnight_blue/style.qss | 13 +++ src/yuzu/game_list.cpp | 23 +++++- src/yuzu/game_list.h | 8 +- src/yuzu/game_list_worker.cpp | 81 ++++++++++++++----- src/yuzu/game_list_worker.h | 8 +- src/yuzu/main.cpp | 24 +++++- src/yuzu/main.h | 2 + 10 files changed, 167 insertions(+), 29 deletions(-) diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 921950c6c0..db55b9b490 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -38,6 +38,18 @@ QPushButton#RendererStatusBarButton:!checked { color: #0066ff; } +QPushButton#RefreshButton { + color: #656565; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; +} + +QPushButton#RefreshButton:hover { + border: 1px solid #76797C; +} + QPushButton#GPUStatusBarButton { color: #656565; border: 1px solid transparent; diff --git a/dist/qt_themes/default_dark/style.qss b/dist/qt_themes/default_dark/style.qss index ca6daa2d52..6a3f517cb6 100644 --- a/dist/qt_themes/default_dark/style.qss +++ b/dist/qt_themes/default_dark/style.qss @@ -42,6 +42,18 @@ QPushButton#RendererStatusBarButton:!checked { color: #00ccdd; } +QPushButton#RefreshButton { + color: #656565; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; +} + +QPushButton#RefreshButton:hover { + border: 1px solid #76797C; +} + QPushButton#GPUStatusBarButton { color: #656565; border: 1px solid transparent; diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 328ac942fc..32610b131e 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1283,6 +1283,19 @@ QPushButton#RendererStatusBarButton:!checked { color: #00ccdd; } +QPushButton#RefreshButton { + min-width: 0px; + color: #656565; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; +} + +QPushButton#RefreshButton:hover { + border: 1px solid #76797C; +} + QPushButton#GPUStatusBarButton { min-width: 0px; color: #656565; diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index eb0889b139..43db5ad0b5 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -2186,6 +2186,19 @@ QPushButton#RendererStatusBarButton:!checked { color: #00ccdd; } +QPushButton#RefreshButton { + min-width: 0px; + color: #656565; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; +} + +QPushButton#RefreshButton:hover { + border: 1px solid #76797C; +} + QPushButton#GPUStatusBarButton { min-width: 0px; color: #656565; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 3788249d93..3fec5b7f61 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -854,7 +854,8 @@ QStandardItemModel* GameList::GetModel() const { return item_model; } -void GameList::PopulateAsync(QVector& game_dirs) { +void GameList::PopulateAsync(QVector& game_dirs, const bool cached) +{ tree_view->setEnabled(false); // Update the columns in case UISettings has changed @@ -871,8 +872,13 @@ void GameList::PopulateAsync(QVector& game_dirs) { item_model->removeRows(0, item_model->rowCount()); search_field->clear(); - current_worker = std::make_unique(vfs, provider, game_dirs, compatibility_list, - play_time_manager, system); + current_worker = std::make_unique(vfs, + provider, + game_dirs, + compatibility_list, + play_time_manager, + system, + cached); // Get events from the worker as data becomes available connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, @@ -901,7 +907,16 @@ const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; -void GameList::RefreshGameDirectory() { +void GameList::ForceRefreshGameDirectory() +{ + if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Force-reloading game list per user request."); + PopulateAsync(UISettings::values.game_dirs, false); + } +} + +void GameList::RefreshGameDirectory() +{ if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); PopulateAsync(UISettings::values.game_dirs); diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index a7f0201709..778333dd87 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -94,7 +94,7 @@ public: bool IsEmpty() const; void LoadCompatibilityList(); - void PopulateAsync(QVector& game_dirs); + void PopulateAsync(QVector& game_dirs, const bool cached = true); void SaveInterfaceLayout(); void LoadInterfaceLayout(); @@ -106,6 +106,10 @@ public: static const QStringList supported_file_extensions; +public slots: + void ForceRefreshGameDirectory(); + void RefreshGameDirectory(); + signals: void BootGame(const QString& game_path, StartGameType type); void GameChosen(const QString& game_path, const u64 title_id = 0); @@ -147,8 +151,6 @@ private: private: void ValidateEntry(const QModelIndex& item); - void RefreshGameDirectory(); - void ToggleFavorite(u64 program_id); void AddFavorite(u64 program_id); void RemoveFavorite(u64 program_id); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 4f977b87be..60109769bf 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -191,12 +191,17 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, return out; } -QList MakeGameListEntry(const std::string& path, const std::string& name, - const std::size_t size, const std::vector& icon, - Loader::AppLoader& loader, u64 program_id, +QList MakeGameListEntry(const std::string& path, + const std::string& name, + const std::size_t size, + const std::vector& icon, + Loader::AppLoader& loader, + u64 program_id, const CompatibilityList& compatibility_list, const PlayTime::PlayTimeManager& play_time_manager, - const FileSys::PatchManager& patch) { + const FileSys::PatchManager& patch, + const bool cached) +{ const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); // The game list uses this as compatibility number for untested games @@ -217,10 +222,17 @@ QList MakeGameListEntry(const std::string& path, const std::stri new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), }; - const auto patch_versions = GetGameListCachedObject( - fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { - return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); - }); + QString patch_versions; + + if (cached) { + patch_versions = GetGameListCachedObject( + fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { + return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); + }); + } else { + patch_versions = FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); + } + list.insert(2, new GameListItem(patch_versions)); return list; @@ -232,10 +244,16 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, QVector& game_dirs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, - Core::System& system_) - : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, - compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{ - system_} { + Core::System& system_, + const bool cached_) + : vfs{std::move(vfs_)} + , provider{provider_} + , game_dirs{game_dirs_} + , compatibility_list{compatibility_list_} + , play_time_manager{play_time_manager_} + , system{system_} + , cached{cached_} +{ // We want the game list to manage our lifetime. setAutoDelete(false); } @@ -323,13 +341,22 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { const PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; + LOG_INFO(Frontend, "PatchManager initiated for id {:X}", program_id); const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); if (control != nullptr) { GetMetadataFromControlNCA(patch, *control, icon, name); } - auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); + auto entry = MakeGameListEntry(file->GetFullPath(), + name, + file->GetSize(), + icon, + *loader, + program_id, + compatibility_list, + play_time_manager, + patch, + cached); RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } @@ -404,9 +431,16 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{id, system.GetFileSystemController(), system.GetContentProvider()}; - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - id, compatibility_list, play_time_manager, patch); + auto entry = MakeGameListEntry(physical_name, + name, + Common::FS::GetSize(physical_name), + icon, + *loader, + id, + compatibility_list, + play_time_manager, + patch, + cached); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); @@ -421,9 +455,16 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); + auto entry = MakeGameListEntry(physical_name, + name, + Common::FS::GetSize(physical_name), + icon, + *loader, + program_id, + compatibility_list, + play_time_manager, + patch, + cached); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index d5990fcde8..0afd7c7849 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -14,6 +14,8 @@ #include #include "common/thread.h" +#include "core/file_sys/registered_cache.h" +#include "uisettings.h" #include "yuzu/compatibility_list.h" #include "yuzu/play_time_manager.h" @@ -22,6 +24,7 @@ class System; } class GameList; +class GameListDir; class QStandardItem; namespace FileSys { @@ -42,7 +45,8 @@ public: QVector& game_dirs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, - Core::System& system_); + Core::System& system_, + const bool cached = true); ~GameListWorker() override; /// Starts the processing of directory tree information. @@ -91,4 +95,6 @@ private: Common::Event processing_completed; Core::System& system; + + const bool cached; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 08e82e4e15..8c58fa5108 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -449,7 +449,8 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) OnCheckFirmwareDecryption(); game_list->LoadCompatibilityList(); - game_list->PopulateAsync(UISettings::values.game_dirs); + // force reload on first load to ensure add-ons get updated + game_list->PopulateAsync(UISettings::values.game_dirs, false); // make sure menubar has the arrow cursor instead of inheriting from this ui->menubar->setCursor(QCursor()); @@ -508,6 +509,10 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) QStringList args = QApplication::arguments(); if (args.size() < 2) { + // Some moron added a race condition to the status bar + // so now we have to make this completely unnecessary call + // to prevent the UI from blowing up. + UpdateUITheme(); return; } @@ -1297,6 +1302,15 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); + // Setup Refresh Button + refresh_button = new QPushButton(); + refresh_button->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); + refresh_button->setObjectName(QStringLiteral("RefreshButton")); + refresh_button->setFocusPolicy(Qt::NoFocus); + connect(refresh_button, &QPushButton::clicked, this, &GMainWindow::OnGameListRefresh); + + statusBar()->insertPermanentWidget(0, refresh_button); + statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); } @@ -2108,6 +2122,7 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP } status_bar_update_timer.start(500); renderer_status_button->setDisabled(true); + refresh_button->setDisabled(true); if (UISettings::values.hide_mouse || Settings::values.mouse_panning) { render_window->installEventFilter(render_window); @@ -2280,6 +2295,7 @@ void GMainWindow::OnEmulationStopped() { game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan); + refresh_button->setEnabled(true); if (!firmware_label->text().isEmpty()) { firmware_label->setVisible(true); @@ -4388,6 +4404,12 @@ void GMainWindow::OnToggleStatusBar() { statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); } +void GMainWindow::OnGameListRefresh() +{ + // force reload add-ons etc + game_list->ForceRefreshGameDirectory(); +} + void GMainWindow::OnAlbum() { constexpr u64 AlbumId = static_cast(Service::AM::AppletProgramId::PhotoViewer); auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index a061b4d68e..6e1a47f5ac 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -391,6 +391,7 @@ private slots: void OnAbout(); void OnToggleFilterBar(); void OnToggleStatusBar(); + void OnGameListRefresh(); void OnDisplayTitleBars(bool); void InitializeHotkeys(); void ToggleFullscreen(); @@ -521,6 +522,7 @@ private: QLabel* firmware_label = nullptr; QPushButton* gpu_accuracy_button = nullptr; QPushButton* renderer_status_button = nullptr; + QPushButton* refresh_button = nullptr; QPushButton* dock_status_button = nullptr; QPushButton* filter_status_button = nullptr; QPushButton* aa_status_button = nullptr;