Add game list refresh button (#126)

nice race condition

Signed-off-by: swurl <swurl@swurl.xyz>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/126
Co-authored-by: swurl <swurl@swurl.xyz>
Co-committed-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-29 08:20:11 +00:00 committed by crueter
parent b78089e978
commit 7f16f12202
10 changed files with 167 additions and 29 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -854,7 +854,8 @@ QStandardItemModel* GameList::GetModel() const {
return item_model;
}
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
void GameList::PopulateAsync(QVector<UISettings::GameDir>& 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<UISettings::GameDir>& game_dirs) {
item_model->removeRows(0, item_model->rowCount());
search_field->clear();
current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list,
play_time_manager, system);
current_worker = std::make_unique<GameListWorker>(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);

View file

@ -94,7 +94,7 @@ public:
bool IsEmpty() const;
void LoadCompatibilityList();
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
void PopulateAsync(QVector<UISettings::GameDir>& 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);

View file

@ -191,12 +191,17 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
return out;
}
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
const std::size_t size, const std::vector<u8>& icon,
Loader::AppLoader& loader, u64 program_id,
QList<QStandardItem*> MakeGameListEntry(const std::string& path,
const std::string& name,
const std::size_t size,
const std::vector<u8>& 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<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
};
const auto patch_versions = GetGameListCachedObject(
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<UISettings::GameDir>& 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); });

View file

@ -14,6 +14,8 @@
#include <QString>
#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<UISettings::GameDir>& 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;
};

View file

@ -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<u64>(Service::AM::AppletProgramId::PhotoViewer);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();

View file

@ -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;