mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-06-27 16:26:29 +02:00
[desktop] Clean up game list code, fix external watcher crash, and fix macOS flickering (#4106)
- Remove unnecessary icon update code (the UI reloads this stuff anyways); test on Windows please - Cleaned up a bunch of duplicated/unused code within the game list - Fix the game list constantly reloading on macOS * When you reconstruct the entire directory list on the watcher the directoryChanged signal fires on macOS--seems like a behavioral change that occurred somewhere in the 6.8 release cycle--and it would enter an infinite loop very quickly * To fix this, only the differences between the current and old watch list are accounted for on both ends. * Since this bug is now fixed, macOS uses Qt 6.11.1 now. Should theoretically improve our situation. - Fix the external content watcher crashing; the worker would attempt to read files that didn't exist without any bounds since its cache was still pointing to that file. This supersedes and replaces #4099. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4106 Reviewed-by: Lizzie <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
parent
7c0e993b5b
commit
102d254530
12 changed files with 181 additions and 277 deletions
|
|
@ -589,12 +589,7 @@ endif()
|
||||||
# Qt stuff
|
# Qt stuff
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
if (YUZU_USE_BUNDLED_QT)
|
if (YUZU_USE_BUNDLED_QT)
|
||||||
# Qt 6.8+ is broken on macOS (??)
|
|
||||||
if (APPLE)
|
|
||||||
AddQt(Eden-CI/Qt 6.7.3)
|
|
||||||
else()
|
|
||||||
AddQt(Eden-CI/Qt 6.11.1)
|
AddQt(Eden-CI/Qt 6.11.1)
|
||||||
endif()
|
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using system Qt")
|
message(STATUS "Using system Qt")
|
||||||
if (NOT Qt6_DIR)
|
if (NOT Qt6_DIR)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,14 @@ static QPixmap GetDefaultIcon(u32 size) {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QPixmap ThemeIcon(const char* name) {
|
||||||
|
const int size = UISettings::values.folder_icon_size.GetValue();
|
||||||
|
|
||||||
|
return QIcon::fromTheme(QLatin1String(name))
|
||||||
|
.pixmap(size, size)
|
||||||
|
.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
class GameListItem : public QStandardItem {
|
class GameListItem : public QStandardItem {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -296,45 +304,33 @@ public:
|
||||||
UISettings::GameDir* game_dir = &directory;
|
UISettings::GameDir* game_dir = &directory;
|
||||||
setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
|
setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
|
||||||
|
|
||||||
const int icon_size = UISettings::values.folder_icon_size.GetValue();
|
const char* icon_name = nullptr;
|
||||||
|
|
||||||
switch (dir_type) {
|
switch (dir_type) {
|
||||||
case GameListItemType::SdmcDir:
|
case GameListItemType::SdmcDir:
|
||||||
setData(
|
icon_name = "sd_card";
|
||||||
QIcon::fromTheme(QStringLiteral("sd_card"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole);
|
setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole);
|
||||||
break;
|
break;
|
||||||
case GameListItemType::UserNandDir:
|
case GameListItemType::UserNandDir:
|
||||||
setData(
|
icon_name = "chip";
|
||||||
QIcon::fromTheme(QStringLiteral("chip"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole);
|
setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole);
|
||||||
break;
|
break;
|
||||||
case GameListItemType::SysNandDir:
|
case GameListItemType::SysNandDir:
|
||||||
setData(
|
icon_name = "chip";
|
||||||
QIcon::fromTheme(QStringLiteral("chip"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
setData(QObject::tr("System Titles"), Qt::DisplayRole);
|
setData(QObject::tr("System Titles"), Qt::DisplayRole);
|
||||||
break;
|
break;
|
||||||
case GameListItemType::CustomDir: {
|
case GameListItemType::CustomDir: {
|
||||||
const QString path = QString::fromStdString(game_dir->path);
|
const QString path = QString::fromStdString(game_dir->path);
|
||||||
const QString icon_name =
|
icon_name = QFileInfo::exists(path) ? "folder" : "bad_folder";
|
||||||
QFileInfo::exists(path) ? QStringLiteral("folder") : QStringLiteral("bad_folder");
|
|
||||||
setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
|
|
||||||
icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
setData(path, Qt::DisplayRole);
|
setData(path, Qt::DisplayRole);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (icon_name != nullptr)
|
||||||
|
setData(ThemeIcon(icon_name), Qt::DecorationRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
int type() const override {
|
int type() const override {
|
||||||
|
|
@ -357,12 +353,7 @@ public:
|
||||||
explicit GameListAddDir() {
|
explicit GameListAddDir() {
|
||||||
setData(type(), TypeRole);
|
setData(type(), TypeRole);
|
||||||
|
|
||||||
const int icon_size = UISettings::values.folder_icon_size.GetValue();
|
setData(ThemeIcon("list-add"), Qt::DecorationRole);
|
||||||
|
|
||||||
setData(QIcon::fromTheme(QStringLiteral("list-add"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
|
setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,12 +371,7 @@ public:
|
||||||
explicit GameListFavorites() {
|
explicit GameListFavorites() {
|
||||||
setData(type(), TypeRole);
|
setData(type(), TypeRole);
|
||||||
|
|
||||||
const int icon_size = UISettings::values.folder_icon_size.GetValue();
|
setData(ThemeIcon("star"), Qt::DecorationRole);
|
||||||
|
|
||||||
setData(QIcon::fromTheme(QStringLiteral("star"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
setData(QObject::tr("Favorites"), Qt::DisplayRole);
|
setData(QObject::tr("Favorites"), Qt::DisplayRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
#include "qt_common/util/game.h"
|
#include "qt_common/util/game.h"
|
||||||
|
|
||||||
#include "qt_common/game_list/game_list_p.h"
|
#include "qt_common/game_list/game_list_p.h"
|
||||||
#include "qt_common/game_list/worker.h"
|
|
||||||
#include "qt_common/game_list/model.h"
|
#include "qt_common/game_list/model.h"
|
||||||
|
#include "qt_common/game_list/worker.h"
|
||||||
|
|
||||||
GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||||
FileSys::ManualContentProvider* provider_,
|
FileSys::ManualContentProvider* provider_,
|
||||||
|
|
@ -36,6 +36,8 @@ GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||||
connect(external_watcher, &QFileSystemWatcher::directoryChanged, this,
|
connect(external_watcher, &QFileSystemWatcher::directoryChanged, this,
|
||||||
&GameListModel::RefreshExternalContent);
|
&GameListModel::RefreshExternalContent);
|
||||||
|
|
||||||
|
ResetExternalWatcher();
|
||||||
|
|
||||||
insertColumns(0, COLUMN_COUNT);
|
insertColumns(0, COLUMN_COUNT);
|
||||||
RetranslateUI();
|
RetranslateUI();
|
||||||
|
|
||||||
|
|
@ -45,6 +47,8 @@ GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||||
GameListModel::~GameListModel() = default;
|
GameListModel::~GameListModel() = default;
|
||||||
|
|
||||||
void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||||
|
emit PopulatingStarted();
|
||||||
|
|
||||||
current_worker.reset();
|
current_worker.reset();
|
||||||
removeRows(0, rowCount());
|
removeRows(0, rowCount());
|
||||||
|
|
||||||
|
|
@ -57,12 +61,6 @@ void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||||
QThreadPool::globalInstance()->start(current_worker.get());
|
QThreadPool::globalInstance()->start(current_worker.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListModel::StopWorker() {
|
|
||||||
// ~GameListWorker sets stop_requested and blocks until run() finishes, so this returns only
|
|
||||||
// once the worker is no longer touching the content providers.
|
|
||||||
current_worker.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameListModel::WorkerEvent() {
|
void GameListModel::WorkerEvent() {
|
||||||
current_worker->ProcessEvents(this);
|
current_worker->ProcessEvents(this);
|
||||||
}
|
}
|
||||||
|
|
@ -203,24 +201,25 @@ void GameListModel::LoadCompatibilityList() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListModel::RefreshGameDirectory() {
|
void GameListModel::Repopulate() {
|
||||||
ResetExternalWatcher();
|
current_worker.reset();
|
||||||
|
|
||||||
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
|
|
||||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
|
||||||
StopWorker();
|
|
||||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||||
PopulateAsync(UISettings::values.game_dirs);
|
PopulateAsync(UISettings::values.game_dirs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameListModel::RefreshGameDirectory() {
|
||||||
|
ResetExternalWatcher();
|
||||||
|
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
|
||||||
|
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
||||||
|
Repopulate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListModel::RefreshExternalContent() {
|
void GameListModel::RefreshExternalContent() {
|
||||||
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
|
if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
|
||||||
LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache.");
|
LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache.");
|
||||||
StopWorker();
|
|
||||||
QtCommon::Game::ResetMetadata(false);
|
QtCommon::Game::ResetMetadata(false);
|
||||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
Repopulate();
|
||||||
PopulateAsync(UISettings::values.game_dirs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,60 +234,6 @@ void GameListModel::ResetExternalWatcher() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListModel::OnUpdateThemedIcons() {
|
|
||||||
for (int i = 0; i < invisibleRootItem()->rowCount(); i++) {
|
|
||||||
QStandardItem* child = invisibleRootItem()->child(i);
|
|
||||||
|
|
||||||
const int icon_size = UISettings::values.folder_icon_size.GetValue();
|
|
||||||
|
|
||||||
switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) {
|
|
||||||
case GameListItemType::SdmcDir:
|
|
||||||
child->setData(
|
|
||||||
QIcon::fromTheme(QStringLiteral("sd_card"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
|
||||||
case GameListItemType::UserNandDir:
|
|
||||||
case GameListItemType::SysNandDir:
|
|
||||||
child->setData(
|
|
||||||
QIcon::fromTheme(QStringLiteral("chip"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
|
||||||
case GameListItemType::CustomDir: {
|
|
||||||
const UISettings::GameDir& game_dir =
|
|
||||||
UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()];
|
|
||||||
const QString icon_name = QFileInfo::exists(QString::fromStdString(game_dir.path))
|
|
||||||
? QStringLiteral("folder")
|
|
||||||
: QStringLiteral("bad_folder");
|
|
||||||
child->setData(
|
|
||||||
QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
|
|
||||||
icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GameListItemType::AddDir:
|
|
||||||
child->setData(
|
|
||||||
QIcon::fromTheme(QStringLiteral("list-add"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
|
||||||
case GameListItemType::Favorites:
|
|
||||||
child->setData(
|
|
||||||
QIcon::fromTheme(QStringLiteral("star"))
|
|
||||||
.pixmap(icon_size)
|
|
||||||
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
|
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameListModel::RetranslateUI() {
|
void GameListModel::RetranslateUI() {
|
||||||
setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
|
setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
|
||||||
setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
|
setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "frontend_common/play_time_manager.h"
|
#include "frontend_common/play_time_manager.h"
|
||||||
|
|
@ -52,10 +52,6 @@ public:
|
||||||
void DonePopulating(const QStringList& watch_list);
|
void DonePopulating(const QStringList& watch_list);
|
||||||
|
|
||||||
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
|
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
|
||||||
// Stops and joins the running populate worker, if any. Must be called before rebuilding the
|
|
||||||
// content providers (CreateFactories), otherwise the worker keeps scanning a cache that is
|
|
||||||
// being torn down underneath it.
|
|
||||||
void StopWorker();
|
|
||||||
void WorkerEvent();
|
void WorkerEvent();
|
||||||
|
|
||||||
bool IsEmpty() const;
|
bool IsEmpty() const;
|
||||||
|
|
@ -68,7 +64,6 @@ public:
|
||||||
|
|
||||||
void LoadCompatibilityList();
|
void LoadCompatibilityList();
|
||||||
|
|
||||||
void OnUpdateThemedIcons();
|
|
||||||
void RetranslateUI();
|
void RetranslateUI();
|
||||||
|
|
||||||
QFileSystemWatcher* GetWatcher() const;
|
QFileSystemWatcher* GetWatcher() const;
|
||||||
|
|
@ -80,6 +75,7 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void ShowList(bool show);
|
void ShowList(bool show);
|
||||||
void PopulatingCompleted(const QStringList& watch_list);
|
void PopulatingCompleted(const QStringList& watch_list);
|
||||||
|
void PopulatingStarted();
|
||||||
void SaveConfig();
|
void SaveConfig();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -87,6 +83,7 @@ private:
|
||||||
|
|
||||||
void AddFavorite(u64 program_id);
|
void AddFavorite(u64 program_id);
|
||||||
void RemoveFavorite(u64 program_id);
|
void RemoveFavorite(u64 program_id);
|
||||||
|
void Repopulate();
|
||||||
|
|
||||||
bool m_flat = false;
|
bool m_flat = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@
|
||||||
#include "qt_common/config/uisettings.h"
|
#include "qt_common/config/uisettings.h"
|
||||||
#include "qt_common/qt_common.h"
|
#include "qt_common/qt_common.h"
|
||||||
|
|
||||||
#include "yuzu/compatibility_list.h"
|
|
||||||
#include "qt_common/game_list/game_list_p.h"
|
#include "qt_common/game_list/game_list_p.h"
|
||||||
|
#include "yuzu/compatibility_list.h"
|
||||||
|
|
||||||
#include "qt_common/game_list/worker.h"
|
|
||||||
#include "qt_common/game_list/model.h"
|
#include "qt_common/game_list/model.h"
|
||||||
|
#include "qt_common/game_list/worker.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -391,6 +391,25 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||||
std::vector<u64> program_ids;
|
std::vector<u64> program_ids;
|
||||||
loader->ReadProgramIds(program_ids);
|
loader->ReadProgramIds(program_ids);
|
||||||
|
|
||||||
|
const auto addEntry = [this, physical_name,
|
||||||
|
parent_dir](std::unique_ptr<Loader::AppLoader>& app_loader,
|
||||||
|
const u64 id) {
|
||||||
|
std::vector<u8> icon;
|
||||||
|
[[maybe_unused]] const auto res1 = app_loader->ReadIcon(icon);
|
||||||
|
|
||||||
|
std::string name = " ";
|
||||||
|
[[maybe_unused]] const auto res3 = app_loader->ReadTitle(name);
|
||||||
|
|
||||||
|
const FileSys::PatchManager patch{id, system.GetFileSystemController(),
|
||||||
|
system.GetContentProvider()};
|
||||||
|
|
||||||
|
auto entry = MakeGameListEntry(
|
||||||
|
physical_name, name, Common::FS::GetSize(physical_name), icon, *app_loader,
|
||||||
|
id, compatibility_list, play_time_manager, patch);
|
||||||
|
|
||||||
|
RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); });
|
||||||
|
};
|
||||||
|
|
||||||
if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 &&
|
if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 &&
|
||||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||||
for (const auto id : program_ids) {
|
for (const auto id : program_ids) {
|
||||||
|
|
@ -404,38 +423,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> icon;
|
addEntry(loader, id);
|
||||||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon);
|
|
||||||
|
|
||||||
std::string name = " ";
|
|
||||||
[[maybe_unused]] const auto res3 = loader->ReadTitle(name);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
RecordEvent(
|
|
||||||
[=](GameListModel* model) { model->AddEntry(entry, parent_dir); });
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::vector<u8> icon;
|
addEntry(loader, program_id);
|
||||||
[[maybe_unused]] const auto res1 = loader->ReadIcon(icon);
|
|
||||||
|
|
||||||
std::string name = " ";
|
|
||||||
[[maybe_unused]] const auto res3 = loader->ReadTitle(name);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
RecordEvent(
|
|
||||||
[=](GameListModel* model) { model->AddEntry(entry, parent_dir); });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (is_dir) {
|
} else if (is_dir) {
|
||||||
|
|
@ -466,29 +457,33 @@ void GameListWorker::run() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameListDir* game_list_dir;
|
||||||
|
bool scan = false;
|
||||||
|
|
||||||
if (game_dir.path == std::string("SDMC")) {
|
if (game_dir.path == std::string("SDMC")) {
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
|
game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
|
||||||
DirEntryReady(game_list_dir);
|
|
||||||
AddTitlesToGameList(game_list_dir);
|
|
||||||
} else if (game_dir.path == std::string("UserNAND")) {
|
} else if (game_dir.path == std::string("UserNAND")) {
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
|
game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
|
||||||
DirEntryReady(game_list_dir);
|
|
||||||
AddTitlesToGameList(game_list_dir);
|
|
||||||
} else if (game_dir.path == std::string("SysNAND")) {
|
} else if (game_dir.path == std::string("SysNAND")) {
|
||||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
|
game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
|
||||||
DirEntryReady(game_list_dir);
|
|
||||||
AddTitlesToGameList(game_list_dir);
|
|
||||||
} else {
|
} else {
|
||||||
const QString qpath = QString::fromStdString(game_dir.path);
|
const QString qpath = QString::fromStdString(game_dir.path);
|
||||||
if (QDir(qpath).exists()) {
|
if (QDir(qpath).exists()) {
|
||||||
watch_list.append(qpath);
|
watch_list.append(qpath);
|
||||||
}
|
}
|
||||||
auto* const game_list_dir = new GameListDir(game_dir);
|
|
||||||
|
game_list_dir = new GameListDir(game_dir);
|
||||||
|
scan = true;
|
||||||
|
}
|
||||||
|
|
||||||
DirEntryReady(game_list_dir);
|
DirEntryReady(game_list_dir);
|
||||||
|
if (scan) {
|
||||||
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
|
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
|
||||||
game_list_dir);
|
game_list_dir);
|
||||||
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan,
|
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan,
|
||||||
game_list_dir);
|
game_list_dir);
|
||||||
|
} else {
|
||||||
|
AddTitlesToGameList(game_list_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,8 @@ add_executable(yuzu
|
||||||
configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui
|
configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui
|
||||||
|
|
||||||
render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui
|
render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui
|
||||||
updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui)
|
updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui
|
||||||
|
game/common.h)
|
||||||
|
|
||||||
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
|
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
|
||||||
|
|
||||||
|
|
|
||||||
36
src/yuzu/game/common.h
Normal file
36
src/yuzu/game/common.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QStandardItem>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include "qt_common/game_list/game_list_p.h"
|
||||||
|
|
||||||
|
namespace Yuzu {
|
||||||
|
|
||||||
|
inline bool ContainsAllWords(const QString& haystack, const QString& userinput) {
|
||||||
|
const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
|
||||||
|
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
||||||
|
[&haystack](const QString& s) { return haystack.contains(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool FilterMatches(const QString& filter, const QStandardItem* item) {
|
||||||
|
if (filter.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const auto program_id = item->data(GameListItemPath::ProgramIdRole).toULongLong();
|
||||||
|
|
||||||
|
const QString file_path = item->data(GameListItemPath::FullPathRole).toString().toLower();
|
||||||
|
const QString file_title = item->data(GameListItemPath::TitleRole).toString().toLower();
|
||||||
|
const QString file_program_id = QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
|
||||||
|
|
||||||
|
const QString file_name =
|
||||||
|
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title;
|
||||||
|
|
||||||
|
return Yuzu::ContainsAllWords(file_name, filter) ||
|
||||||
|
(file_program_id.size() == 16 && file_program_id.contains(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Yuzu
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
#include <QScrollerProperties>
|
#include <QScrollerProperties>
|
||||||
|
|
||||||
#include "qt_common/config/uisettings.h"
|
#include "qt_common/config/uisettings.h"
|
||||||
|
#include "qt_common/game_list/model.h"
|
||||||
|
#include "yuzu/game/common.h"
|
||||||
#include "yuzu/game/game_card.h"
|
#include "yuzu/game/game_card.h"
|
||||||
#include "yuzu/game/game_grid.h"
|
#include "yuzu/game/game_grid.h"
|
||||||
#include "qt_common/game_list/game_list_p.h"
|
|
||||||
#include "qt_common/game_list/model.h"
|
|
||||||
|
|
||||||
GameGrid::GameGrid(QWidget* parent) : QListView{parent} {
|
GameGrid::GameGrid(QWidget* parent) : QListView{parent} {
|
||||||
m_gameCard = new GameCard(this);
|
m_gameCard = new GameCard(this);
|
||||||
|
|
@ -43,26 +43,12 @@ void GameGrid::SetModel(GameListModel* model) {
|
||||||
void GameGrid::ApplyFilter(const QString& edit_filter_text, GameListModel* model) {
|
void GameGrid::ApplyFilter(const QString& edit_filter_text, GameListModel* model) {
|
||||||
int row_count = model->rowCount();
|
int row_count = model->rowCount();
|
||||||
|
|
||||||
auto ContainsAllWords = [](const QString& haystack, const QString& userinput) {
|
|
||||||
const QStringList userinput_split =
|
|
||||||
userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
|
|
||||||
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
|
||||||
[&haystack](const QString& s) { return haystack.contains(s); });
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < row_count; ++i) {
|
for (int i = 0; i < row_count; ++i) {
|
||||||
QStandardItem* item = model->item(i, 0);
|
QStandardItem* item = model->item(i, 0);
|
||||||
if (!item)
|
if (!item)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const QString file_path =
|
if (Yuzu::FilterMatches(edit_filter_text, item)) {
|
||||||
item->data(GameListItemPath::FullPathRole).toString().toLower();
|
|
||||||
const QString file_title =
|
|
||||||
item->data(GameListItemPath::TitleRole).toString().toLower();
|
|
||||||
const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) +
|
|
||||||
QLatin1Char{' '} + file_title;
|
|
||||||
|
|
||||||
if (edit_filter_text.isEmpty() || ContainsAllWords(file_name, edit_filter_text)) {
|
|
||||||
setRowHidden(i, false);
|
setRowHidden(i, false);
|
||||||
} else {
|
} else {
|
||||||
setRowHidden(i, true);
|
setRowHidden(i, true);
|
||||||
|
|
|
||||||
|
|
@ -22,22 +22,21 @@
|
||||||
#include <QVariantAnimation>
|
#include <QVariantAnimation>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging.h"
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/file_sys/patch_manager.h"
|
#include "core/file_sys/patch_manager.h"
|
||||||
#include "core/file_sys/registered_cache.h"
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "qt_common/config/uisettings.h"
|
#include "qt_common/config/uisettings.h"
|
||||||
|
#include "qt_common/game_list/game_list_p.h"
|
||||||
|
#include "qt_common/game_list/model.h"
|
||||||
#include "qt_common/qt_common.h"
|
#include "qt_common/qt_common.h"
|
||||||
#include "qt_common/util/game.h"
|
#include "qt_common/util/game.h"
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
#include "yuzu/game/game_list.h"
|
|
||||||
#include "qt_common/game_list/game_list_p.h"
|
|
||||||
#include "yuzu/game/game_grid.h"
|
#include "yuzu/game/game_grid.h"
|
||||||
|
#include "yuzu/game/game_list.h"
|
||||||
#include "yuzu/game/game_tree.h"
|
#include "yuzu/game/game_tree.h"
|
||||||
#include "qt_common/game_list/model.h"
|
#include "yuzu/game/search_field.h"
|
||||||
#include "yuzu/main_window.h"
|
#include "yuzu/main_window.h"
|
||||||
#include "yuzu/util/controller_navigation.h"
|
#include "yuzu/util/controller_navigation.h"
|
||||||
#include "yuzu/game/search_field.h"
|
|
||||||
|
|
||||||
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
|
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
|
||||||
PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
|
PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
|
||||||
|
|
@ -62,8 +61,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
|
||||||
|
|
||||||
SetupScrollAnimation();
|
SetupScrollAnimation();
|
||||||
|
|
||||||
connect(main_window, &MainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
|
|
||||||
|
|
||||||
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
|
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
|
||||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||||
|
|
||||||
|
|
@ -87,6 +84,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
|
||||||
|
|
||||||
connect(item_model, &GameListModel::ShowList, this, &GameList::ShowList);
|
connect(item_model, &GameListModel::ShowList, this, &GameList::ShowList);
|
||||||
connect(item_model, &GameListModel::SaveConfig, this, &GameList::SaveConfig);
|
connect(item_model, &GameListModel::SaveConfig, this, &GameList::SaveConfig);
|
||||||
|
connect(item_model, &GameListModel::PopulatingStarted, this, &GameList::OnPopulate);
|
||||||
|
|
||||||
connect(tree_view, &GameTree::FilterResultReady, search_field,
|
connect(tree_view, &GameTree::FilterResultReady, search_field,
|
||||||
[this](int visible, int total) { search_field->setFilterResult(visible, total); });
|
[this](int visible, int total) { search_field->setFilterResult(visible, total); });
|
||||||
|
|
@ -137,15 +135,17 @@ void GameList::LoadCompatibilityList() {
|
||||||
item_model->LoadCompatibilityList();
|
item_model->LoadCompatibilityList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
void GameList::OnPopulate() {
|
||||||
m_currentView->setEnabled(false);
|
m_currentView->setEnabled(false);
|
||||||
|
|
||||||
tree_view->UpdateColumnVisibility(item_model);
|
if (m_isTreeMode) {
|
||||||
|
|
||||||
if (!m_isTreeMode) {
|
|
||||||
grid_view->UpdateIconSize();
|
grid_view->UpdateIconSize();
|
||||||
|
} else {
|
||||||
|
tree_view->UpdateColumnVisibility(item_model);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||||
item_model->PopulateAsync(game_dirs);
|
item_model->PopulateAsync(game_dirs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,8 +196,10 @@ void GameList::ResetViewMode() {
|
||||||
|
|
||||||
auto scroller = QScroller::scroller(view);
|
auto scroller = QScroller::scroller(view);
|
||||||
QScrollerProperties props;
|
QScrollerProperties props;
|
||||||
props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
|
props.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy,
|
||||||
props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
|
QScrollerProperties::OvershootAlwaysOff);
|
||||||
|
props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy,
|
||||||
|
QScrollerProperties::OvershootAlwaysOff);
|
||||||
scroller->setScrollerProperties(props);
|
scroller->setScrollerProperties(props);
|
||||||
|
|
||||||
if (m_isTreeMode != newTreeMode) {
|
if (m_isTreeMode != newTreeMode) {
|
||||||
|
|
@ -223,10 +225,6 @@ void GameList::OnFilterCloseClicked() {
|
||||||
main_window->filterBarSetChecked(false);
|
main_window->filterBarSetChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::OnUpdateThemedIcons() {
|
|
||||||
item_model->OnUpdateThemedIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameList::OnPopulatingCompleted(const QStringList& watch_list) {
|
void GameList::OnPopulatingCompleted(const QStringList& watch_list) {
|
||||||
emit ShowList(!item_model->IsEmpty());
|
emit ShowList(!item_model->IsEmpty());
|
||||||
|
|
||||||
|
|
@ -255,49 +253,43 @@ void GameList::OnPopulatingCompleted(const QStringList& watch_list) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear out the old directories to watch for changes and add the new ones
|
// Watcher updates
|
||||||
auto* watcher = item_model->GetWatcher();
|
auto* watcher = item_model->GetWatcher();
|
||||||
const QStringList current_watch = watcher->directories();
|
auto current_watch_list = watcher->directories();
|
||||||
|
|
||||||
constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
|
constexpr qsizetype LIMIT_WATCH_DIRECTORIES = 5000;
|
||||||
constexpr int SLICE_SIZE = 25;
|
constexpr int SLICE_SIZE = 25;
|
||||||
|
|
||||||
QStringList desired_watch = watch_list;
|
QStringList to_remove, to_add;
|
||||||
if (desired_watch.size() > LIMIT_WATCH_DIRECTORIES) {
|
|
||||||
desired_watch = desired_watch.mid(0, LIMIT_WATCH_DIRECTORIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only re-arm the watcher when the set of directories actually changed. Re-adding the same
|
const auto slice = [&](const QStringList& list, std::function<void(const QStringList&)> callback) {
|
||||||
// paths makes the macOS QFileSystemWatcher re-emit directoryChanged (the FSEvent arrives
|
const int len = (std::min)(list.size(), LIMIT_WATCH_DIRECTORIES);
|
||||||
// asynchronously, so the blockSignals guard below does not catch it), which retriggers a full
|
for (int i = 0; i < len; i += SLICE_SIZE) {
|
||||||
// refresh and loops forever, making the game list visibly flash. Comparing the sets breaks
|
auto chunk = list.mid(i, SLICE_SIZE);
|
||||||
// that loop: at steady state we leave the watcher untouched and no spurious event is produced.
|
|
||||||
QStringList sorted_current = current_watch;
|
|
||||||
QStringList sorted_desired = desired_watch;
|
|
||||||
sorted_current.sort();
|
|
||||||
sorted_desired.sort();
|
|
||||||
|
|
||||||
if (sorted_current != sorted_desired) {
|
|
||||||
if (!current_watch.isEmpty()) {
|
|
||||||
watcher->removePaths(current_watch);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
const bool old_signals_blocked = watcher->blockSignals(true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (int i = 0; i < desired_watch.size(); i += SLICE_SIZE) {
|
|
||||||
auto chunk = desired_watch.mid(i, SLICE_SIZE);
|
|
||||||
if (!chunk.isEmpty()) {
|
if (!chunk.isEmpty()) {
|
||||||
watcher->addPaths(chunk);
|
callback(chunk);
|
||||||
}
|
}
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#ifdef __APPLE__
|
// remove any paths not in the new watch list
|
||||||
watcher->blockSignals(old_signals_blocked);
|
for (const auto& path : std::as_const(current_watch_list)) {
|
||||||
#endif
|
if (!watch_list.contains(path)) {
|
||||||
|
to_remove.emplaceBack(path);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slice(to_remove, [watcher](const QStringList& chunk) { watcher->removePaths(chunk); });
|
||||||
|
|
||||||
|
// add any paths not in the old watch list
|
||||||
|
for (const auto& path : std::as_const(watch_list)) {
|
||||||
|
if (!current_watch_list.contains(path)) {
|
||||||
|
to_add.emplaceBack(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slice(to_add, [watcher](const QStringList& chunk) { watcher->addPaths(chunk); });
|
||||||
|
|
||||||
m_currentView->setEnabled(true);
|
m_currentView->setEnabled(true);
|
||||||
|
|
||||||
|
|
@ -317,23 +309,16 @@ void GameList::OnPopulatingCompleted(const QStringList& watch_list) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::RefreshGameDirectory() {
|
void GameList::RefreshGameDirectory() {
|
||||||
item_model->ResetExternalWatcher();
|
item_model->RefreshGameDirectory();
|
||||||
|
|
||||||
if (!UISettings::values.game_dirs.empty()) {
|
|
||||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
|
||||||
item_model->StopWorker();
|
|
||||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
|
||||||
PopulateAsync(UISettings::values.game_dirs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::RefreshExternalContent() {
|
void GameList::RefreshExternalContent() {
|
||||||
if (!UISettings::values.game_dirs.empty()) {
|
item_model->RefreshExternalContent();
|
||||||
LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache.");
|
}
|
||||||
item_model->StopWorker();
|
|
||||||
QtCommon::Game::ResetMetadata(false);
|
void GameList::UpdateIconSizes() {
|
||||||
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
if (!m_isTreeMode) {
|
||||||
PopulateAsync(UISettings::values.game_dirs);
|
grid_view->UpdateIconSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,9 +476,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
||||||
});
|
});
|
||||||
connect(start_game, &QAction::triggered, this,
|
connect(start_game, &QAction::triggered, this,
|
||||||
[this, path]() { emit BootGame(QString::fromStdString(path), StartGameType::Normal); });
|
[this, path]() { emit BootGame(QString::fromStdString(path), StartGameType::Normal); });
|
||||||
connect(start_game_global, &QAction::triggered, this, [this, path]() {
|
connect(start_game_global, &QAction::triggered, this,
|
||||||
emit BootGame(QString::fromStdString(path), StartGameType::Global);
|
[this, path]() { emit BootGame(QString::fromStdString(path), StartGameType::Global); });
|
||||||
});
|
|
||||||
connect(open_mod_location, &QAction::triggered, this, [this, program_id, path]() {
|
connect(open_mod_location, &QAction::triggered, this, [this, program_id, path]() {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
|
||||||
});
|
});
|
||||||
|
|
@ -632,7 +616,8 @@ void GameList::AddFavoritesPopup(QMenu& context_menu) {
|
||||||
|
|
||||||
connect(clear, &QAction::triggered, this, [this] {
|
connect(clear, &QAction::triggered, this, [this] {
|
||||||
UISettings::values.favorited_ids.clear();
|
UISettings::values.favorited_ids.clear();
|
||||||
item_model->invisibleRootItem()->child(0)->removeRows(0, item_model->invisibleRootItem()->child(0)->rowCount());
|
item_model->invisibleRootItem()->child(0)->removeRows(
|
||||||
|
0, item_model->invisibleRootItem()->child(0)->rowCount());
|
||||||
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
|
tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -731,9 +716,6 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
GameListPlaceholder::GameListPlaceholder(MainWindow* parent) : QWidget{parent} {
|
GameListPlaceholder::GameListPlaceholder(MainWindow* parent) : QWidget{parent} {
|
||||||
connect(parent, &MainWindow::UpdateThemedIcons, this,
|
|
||||||
&GameListPlaceholder::onUpdateThemedIcons);
|
|
||||||
|
|
||||||
layout = new QVBoxLayout;
|
layout = new QVBoxLayout;
|
||||||
image = new QLabel;
|
image = new QLabel;
|
||||||
text = new QLabel;
|
text = new QLabel;
|
||||||
|
|
@ -754,10 +736,6 @@ GameListPlaceholder::GameListPlaceholder(MainWindow* parent) : QWidget{parent} {
|
||||||
|
|
||||||
GameListPlaceholder::~GameListPlaceholder() = default;
|
GameListPlaceholder::~GameListPlaceholder() = default;
|
||||||
|
|
||||||
void GameListPlaceholder::onUpdateThemedIcons() {
|
|
||||||
image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
|
void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
|
||||||
emit GameListPlaceholder::AddDirectory();
|
emit GameListPlaceholder::AddDirectory();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ public:
|
||||||
public slots:
|
public slots:
|
||||||
void RefreshGameDirectory();
|
void RefreshGameDirectory();
|
||||||
void RefreshExternalContent();
|
void RefreshExternalContent();
|
||||||
|
void UpdateIconSizes();
|
||||||
|
void OnPopulate();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void BootGame(const QString& game_path, StartGameType type);
|
void BootGame(const QString& game_path, StartGameType type);
|
||||||
|
|
@ -119,7 +121,6 @@ signals:
|
||||||
private slots:
|
private slots:
|
||||||
void OnTextChanged(const QString& new_text);
|
void OnTextChanged(const QString& new_text);
|
||||||
void OnFilterCloseClicked();
|
void OnFilterCloseClicked();
|
||||||
void OnUpdateThemedIcons();
|
|
||||||
void OnPopulatingCompleted(const QStringList& watch_list);
|
void OnPopulatingCompleted(const QStringList& watch_list);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -175,9 +176,6 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void AddDirectory();
|
void AddDirectory();
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onUpdateThemedIcons();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@
|
||||||
|
|
||||||
#include "qt_common/config/uisettings.h"
|
#include "qt_common/config/uisettings.h"
|
||||||
#include "qt_common/game_list/game_list_p.h"
|
#include "qt_common/game_list/game_list_p.h"
|
||||||
#include "yuzu/game/game_tree.h"
|
|
||||||
#include "qt_common/game_list/model.h"
|
#include "qt_common/game_list/model.h"
|
||||||
|
#include "yuzu/game/common.h"
|
||||||
|
#include "yuzu/game/game_tree.h"
|
||||||
|
|
||||||
GameTree::GameTree(QWidget* parent) : QTreeView{parent} {
|
GameTree::GameTree(QWidget* parent) : QTreeView{parent} {
|
||||||
setAlternatingRowColors(true);
|
setAlternatingRowColors(true);
|
||||||
|
|
@ -139,28 +140,7 @@ void GameTree::ApplyFilter(const QString& edit_filter_text, GameListModel* model
|
||||||
|
|
||||||
const QStandardItem* child = folder->child(j, 0);
|
const QStandardItem* child = folder->child(j, 0);
|
||||||
|
|
||||||
const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong();
|
if (Yuzu::FilterMatches(edit_filter_text, child)) {
|
||||||
|
|
||||||
const QString file_path =
|
|
||||||
child->data(GameListItemPath::FullPathRole).toString().toLower();
|
|
||||||
const QString file_title =
|
|
||||||
child->data(GameListItemPath::TitleRole).toString().toLower();
|
|
||||||
const QString file_program_id =
|
|
||||||
QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
|
|
||||||
|
|
||||||
const QString file_name =
|
|
||||||
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
|
|
||||||
file_title;
|
|
||||||
|
|
||||||
auto ContainsAllWords = [](const QString& haystack, const QString& userinput) {
|
|
||||||
const QStringList userinput_split =
|
|
||||||
userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts);
|
|
||||||
return std::all_of(userinput_split.begin(), userinput_split.end(),
|
|
||||||
[&haystack](const QString& s) { return haystack.contains(s); });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ContainsAllWords(file_name, edit_filter_text) ||
|
|
||||||
(file_program_id.size() == 16 && file_program_id.contains(edit_filter_text))) {
|
|
||||||
setRowHidden(j, folder_index, false);
|
setRowHidden(j, folder_index, false);
|
||||||
++result_count;
|
++result_count;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -579,9 +579,12 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
||||||
} else if (should_launch_qlaunch) {
|
} else if (should_launch_qlaunch) {
|
||||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
||||||
} else if (should_launch_hlaunch) {
|
} else if (should_launch_hlaunch) {
|
||||||
std::filesystem::path const sd_dir = Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
std::filesystem::path const sd_dir =
|
||||||
|
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
||||||
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
||||||
BootGame(QString::fromStdString(hbl_path), LibraryAppletParameters(0x010000000000100Dull, Service::AM::AppletId::QLaunch));
|
BootGame(
|
||||||
|
QString::fromStdString(hbl_path),
|
||||||
|
LibraryAppletParameters(0x010000000000100Dull, Service::AM::AppletId::QLaunch));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1545,6 +1548,8 @@ void MainWindow::ConnectMenuEvents() {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
UISettings::values.game_icon_size.SetValue(size);
|
UISettings::values.game_icon_size.SetValue(size);
|
||||||
CheckIconSize();
|
CheckIconSize();
|
||||||
|
|
||||||
|
game_list->UpdateIconSizes();
|
||||||
game_list->RefreshGameDirectory();
|
game_list->RefreshGameDirectory();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -3363,6 +3368,7 @@ void MainWindow::ToggleShowGameName() {
|
||||||
|
|
||||||
CheckIconSize();
|
CheckIconSize();
|
||||||
|
|
||||||
|
game_list->UpdateIconSizes();
|
||||||
game_list->RefreshGameDirectory();
|
game_list->RefreshGameDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3868,6 +3874,7 @@ void MainWindow::OnGameListRefresh() {
|
||||||
// Resets metadata cache and reloads
|
// Resets metadata cache and reloads
|
||||||
QtCommon::Game::ResetMetadata(false);
|
QtCommon::Game::ResetMetadata(false);
|
||||||
game_list->RefreshGameDirectory();
|
game_list->RefreshGameDirectory();
|
||||||
|
game_list->RefreshExternalContent();
|
||||||
SetFirmwareVersion();
|
SetFirmwareVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue