summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt58
-rw-r--r--src/yuzu/applets/web_browser.cpp4
-rw-r--r--src/yuzu/applets/web_browser.h4
-rw-r--r--src/yuzu/configuration/config.cpp130
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_debug.cpp12
-rw-r--r--src/yuzu/configuration/configure_debug.ui72
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp46
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp177
-rw-r--r--src/yuzu/configuration/configure_filesystem.h43
-rw-r--r--src/yuzu/configuration/configure_filesystem.ui395
-rw-r--r--src/yuzu/configuration/configure_gamelist.cpp2
-rw-r--r--src/yuzu/configuration/configure_general.cpp19
-rw-r--r--src/yuzu/configuration/configure_general.ui50
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.ui27
-rw-r--r--src/yuzu/configuration/configure_input.cpp14
-rw-r--r--src/yuzu/configuration/configure_input.ui2
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp27
-rw-r--r--src/yuzu/configuration/configure_input_simple.cpp4
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.cpp6
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp2
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp8
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.cpp2
-rw-r--r--src/yuzu/configuration/configure_web.cpp2
-rw-r--r--src/yuzu/debugger/console.cpp2
-rw-r--r--src/yuzu/discord_impl.cpp2
-rw-r--r--src/yuzu/game_list.cpp441
-rw-r--r--src/yuzu/game_list.h43
-rw-r--r--src/yuzu/game_list_p.h129
-rw-r--r--src/yuzu/game_list_worker.cpp88
-rw-r--r--src/yuzu/game_list_worker.h26
-rw-r--r--src/yuzu/hotkeys.cpp2
-rw-r--r--src/yuzu/main.cpp249
-rw-r--r--src/yuzu/main.h13
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.cpp (renamed from src/yuzu/ui_settings.cpp)2
-rw-r--r--src/yuzu/uisettings.h (renamed from src/yuzu/ui_settings.h)21
38 files changed, 1723 insertions, 419 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 3dc0e47d0..dc6fa07fc 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -1,5 +1,6 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
@@ -7,6 +8,7 @@ add_executable(yuzu
Info.plist
about_dialog.cpp
about_dialog.h
+ aboutdialog.ui
applets/error.cpp
applets/error.h
applets/profile_select.cpp
@@ -17,42 +19,62 @@ add_executable(yuzu
applets/web_browser.h
bootmanager.cpp
bootmanager.h
+ compatdb.ui
compatibility_list.cpp
compatibility_list.h
configuration/config.cpp
configuration/config.h
+ configuration/configure.ui
configuration/configure_audio.cpp
configuration/configure_audio.h
+ configuration/configure_audio.ui
configuration/configure_debug.cpp
configuration/configure_debug.h
+ configuration/configure_debug.ui
configuration/configure_dialog.cpp
configuration/configure_dialog.h
+ configuration/configure_filesystem.cpp
+ configuration/configure_filesystem.h
+ configuration/configure_filesystem.ui
configuration/configure_gamelist.cpp
configuration/configure_gamelist.h
+ configuration/configure_gamelist.ui
configuration/configure_general.cpp
configuration/configure_general.h
+ configuration/configure_general.ui
configuration/configure_graphics.cpp
configuration/configure_graphics.h
+ configuration/configure_graphics.ui
configuration/configure_hotkeys.cpp
configuration/configure_hotkeys.h
+ configuration/configure_hotkeys.ui
configuration/configure_input.cpp
configuration/configure_input.h
+ configuration/configure_input.ui
configuration/configure_input_player.cpp
configuration/configure_input_player.h
+ configuration/configure_input_player.ui
configuration/configure_input_simple.cpp
configuration/configure_input_simple.h
+ configuration/configure_input_simple.ui
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
+ configuration/configure_mouse_advanced.ui
+ configuration/configure_per_general.cpp
+ configuration/configure_per_general.h
+ configuration/configure_per_general.ui
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
+ configuration/configure_profile_manager.ui
configuration/configure_system.cpp
configuration/configure_system.h
- configuration/configure_per_general.cpp
- configuration/configure_per_general.h
+ configuration/configure_system.ui
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
+ configuration/configure_touchscreen_advanced.ui
configuration/configure_web.cpp
configuration/configure_web.h
+ configuration/configure_web.ui
debugger/graphics/graphics_breakpoint_observer.cpp
debugger/graphics/graphics_breakpoint_observer.h
debugger/graphics/graphics_breakpoints.cpp
@@ -72,12 +94,14 @@ add_executable(yuzu
game_list_worker.h
loading_screen.cpp
loading_screen.h
+ loading_screen.ui
hotkeys.cpp
hotkeys.h
main.cpp
main.h
- ui_settings.cpp
- ui_settings.h
+ main.ui
+ uisettings.cpp
+ uisettings.h
util/limitable_input_dialog.cpp
util/limitable_input_dialog.h
util/sequence_dialog/sequence_dialog.cpp
@@ -89,44 +113,18 @@ add_executable(yuzu
yuzu.rc
)
-set(UIS
- aboutdialog.ui
- configuration/configure.ui
- configuration/configure_audio.ui
- configuration/configure_debug.ui
- configuration/configure_gamelist.ui
- configuration/configure_general.ui
- configuration/configure_graphics.ui
- configuration/configure_hotkeys.ui
- configuration/configure_input.ui
- configuration/configure_input_player.ui
- configuration/configure_input_simple.ui
- configuration/configure_mouse_advanced.ui
- configuration/configure_per_general.ui
- configuration/configure_profile_manager.ui
- configuration/configure_system.ui
- configuration/configure_touchscreen_advanced.ui
- configuration/configure_web.ui
- compatdb.ui
- loading_screen.ui
- main.ui
-)
-
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
-qt5_wrap_ui(UI_HDRS ${UIS})
target_sources(yuzu
PRIVATE
${COMPAT_LIST}
${ICONS}
${THEMES}
- ${UI_HDRS}
- ${UIS}
)
if (APPLE)
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp
index ac80b2fa2..33f1c385d 100644
--- a/src/yuzu/applets/web_browser.cpp
+++ b/src/yuzu/applets/web_browser.cpp
@@ -87,8 +87,8 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
QtWebBrowser::~QtWebBrowser() = default;
-void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) {
+void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) {
this->unpack_romfs_callback = std::move(unpack_romfs_callback);
this->finished_callback = std::move(finished_callback);
diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h
index 1a3d67353..b38437e46 100644
--- a/src/yuzu/applets/web_browser.h
+++ b/src/yuzu/applets/web_browser.h
@@ -37,8 +37,8 @@ public:
explicit QtWebBrowser(GMainWindow& main_window);
~QtWebBrowser() override;
- void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) override;
+ void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
+ std::function<void()> finished_callback) override;
signals:
void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 10e5c5c38..92d9fb161 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -11,7 +11,7 @@
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "yuzu/configuration/config.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@@ -436,7 +436,6 @@ void Config::ReadControlValues() {
void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool();
qt_config->endGroup();
@@ -460,6 +459,49 @@ void Config::ReadDataStorageValues() {
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
.toString()
.toStdString());
+ FileUtil::GetUserPath(
+ FileUtil::UserPath::LoadDir,
+ qt_config
+ ->value(QStringLiteral("load_directory"),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)))
+ .toString()
+ .toStdString());
+ FileUtil::GetUserPath(
+ FileUtil::UserPath::DumpDir,
+ qt_config
+ ->value(QStringLiteral("dump_directory"),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)))
+ .toString()
+ .toStdString());
+ FileUtil::GetUserPath(
+ FileUtil::UserPath::CacheDir,
+ qt_config
+ ->value(QStringLiteral("cache_directory"),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)))
+ .toString()
+ .toStdString());
+ Settings::values.gamecard_inserted =
+ ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
+ Settings::values.gamecard_current_game =
+ ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool();
+ Settings::values.gamecard_path =
+ ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString();
+ Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
+ ReadSetting(QStringLiteral("nand_total_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)))
+ .toULongLong());
+ Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
+ ReadSetting(QStringLiteral("nand_user_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)))
+ .toULongLong());
+ Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
+ ReadSetting(QStringLiteral("nand_system_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)))
+ .toULongLong());
+ Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
+ ReadSetting(QStringLiteral("sdmc_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)))
+ .toULongLong());
qt_config->endGroup();
}
@@ -467,12 +509,18 @@ void Config::ReadDataStorageValues() {
void Config::ReadDebuggingValues() {
qt_config->beginGroup(QStringLiteral("Debugging"));
+ // Intentionally not using the QT default setting as this is intended to be changed in the ini
+ Settings::values.record_frame_times =
+ qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
Settings::values.program_args =
ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString();
Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
+ Settings::values.reporting_services =
+ ReadSetting(QStringLiteral("reporting_services"), false).toBool();
+ Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool();
qt_config->endGroup();
}
@@ -514,10 +562,38 @@ void Config::ReadPathValues() {
UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
- UISettings::values.game_directory_path =
+ UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
+ UISettings::values.game_dir_deprecated =
ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
- UISettings::values.game_directory_deepscan =
+ UISettings::values.game_dir_deprecated_deepscan =
ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
+ const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
+ for (int i = 0; i < gamedirs_size; ++i) {
+ qt_config->setArrayIndex(i);
+ UISettings::GameDir game_dir;
+ game_dir.path = ReadSetting(QStringLiteral("path")).toString();
+ game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
+ game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
+ UISettings::values.game_dirs.append(game_dir);
+ }
+ qt_config->endArray();
+ // create NAND and SD card directories if empty, these are not removable through the UI,
+ // also carries over old game list settings if present
+ if (UISettings::values.game_dirs.isEmpty()) {
+ UISettings::GameDir game_dir;
+ game_dir.path = QStringLiteral("SDMC");
+ game_dir.expanded = true;
+ UISettings::values.game_dirs.append(game_dir);
+ game_dir.path = QStringLiteral("UserNAND");
+ UISettings::values.game_dirs.append(game_dir);
+ game_dir.path = QStringLiteral("SysNAND");
+ UISettings::values.game_dirs.append(game_dir);
+ if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
+ game_dir.path = UISettings::values.game_dir_deprecated;
+ game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
+ UISettings::values.game_dirs.append(game_dir);
+ }
+ }
UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
qt_config->endGroup();
@@ -691,7 +767,7 @@ void Config::ReadValues() {
ReadDataStorageValues();
ReadSystemValues();
ReadMiscellaneousValues();
- ReadDebugValues();
+ ReadDebuggingValues();
ReadWebServiceValues();
ReadDisabledAddOnValues();
ReadUIValues();
@@ -827,7 +903,6 @@ void Config::SaveControlValues() {
void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false);
qt_config->endGroup();
@@ -843,19 +918,47 @@ void Config::SaveDataStorageValues() {
WriteSetting(QStringLiteral("sdmc_directory"),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
-
+ WriteSetting(QStringLiteral("load_directory"),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
+ WriteSetting(QStringLiteral("dump_directory"),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
+ WriteSetting(QStringLiteral("cache_directory"),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+ WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
+ WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
+ false);
+ WriteSetting(QStringLiteral("gamecard_path"),
+ QString::fromStdString(Settings::values.gamecard_path), QStringLiteral(""));
+ WriteSetting(QStringLiteral("nand_total_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)));
+ WriteSetting(QStringLiteral("nand_user_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)));
+ WriteSetting(QStringLiteral("nand_system_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)));
+ WriteSetting(QStringLiteral("sdmc_size"),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)),
+ QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)));
qt_config->endGroup();
}
void Config::SaveDebuggingValues() {
qt_config->beginGroup(QStringLiteral("Debugging"));
+ // Intentionally not using the QT default setting as this is intended to be changed in the ini
+ qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
WriteSetting(QStringLiteral("program_args"),
QString::fromStdString(Settings::values.program_args), QStringLiteral(""));
WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false);
+ WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false);
qt_config->endGroup();
}
@@ -896,10 +999,15 @@ void Config::SavePathValues() {
WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
- WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path,
- QStringLiteral("."));
- WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan,
- false);
+ qt_config->beginWriteArray(QStringLiteral("gamedirs"));
+ for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
+ qt_config->setArrayIndex(i);
+ const auto& game_dir = UISettings::values.game_dirs[i];
+ WriteSetting(QStringLiteral("path"), game_dir.path);
+ WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
+ WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
+ }
+ qt_config->endArray();
WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 267717bc9..49fadd0ef 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -63,6 +63,11 @@
<string>Profiles</string>
</attribute>
</widget>
+ <widget class="ConfigureFilesystem" name="filesystemTab">
+ <attribute name="title">
+ <string>Filesystem</string>
+ </attribute>
+ </widget>
<widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
@@ -126,6 +131,12 @@
<container>1</container>
</customwidget>
<customwidget>
+ <class>ConfigureFilesystem</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_filesystem.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
<class>ConfigureAudio</class>
<extends>QWidget</extends>
<header>configuration/configure_audio.h</header>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index efc2bedfd..90c1f9459 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -12,13 +12,13 @@
#include "ui_configure_debug.h"
#include "yuzu/configuration/configure_debug.h"
#include "yuzu/debugger/console.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {
ui->setupUi(this);
SetConfiguration();
- connect(ui->open_log_button, &QPushButton::pressed, []() {
+ connect(ui->open_log_button, &QPushButton::clicked, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
@@ -34,8 +34,8 @@ void ConfigureDebug::SetConfiguration() {
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
- ui->dump_exefs->setChecked(Settings::values.dump_exefs);
- ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
+ ui->reporting_services->setChecked(Settings::values.reporting_services);
+ ui->quest_flag->setChecked(Settings::values.quest_flag);
}
void ConfigureDebug::ApplyConfiguration() {
@@ -44,8 +44,8 @@ void ConfigureDebug::ApplyConfiguration() {
UISettings::values.show_console = ui->toggle_console->isChecked();
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
- Settings::values.dump_exefs = ui->dump_exefs->isChecked();
- Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked();
+ Settings::values.reporting_services = ui->reporting_services->isChecked();
+ Settings::values.quest_flag = ui->quest_flag->isChecked();
Debugger::ToggleConsole();
Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 5ca9ce0e6..ce49569bb 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
- <height>357</height>
+ <height>474</height>
</rect>
</property>
<property name="windowTitle">
@@ -103,6 +103,44 @@
</item>
</layout>
</item>
+ <item>
+ <widget class="QCheckBox" name="reporting_services">
+ <property name="text">
+ <string>Enable Verbose Reporting Services</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>This will be reset automatically when yuzu closes.</string>
+ </property>
+ <property name="indent">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>Advanced</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="quest_flag">
+ <property name="text">
+ <string>Kiosk (Quest) Mode</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -130,39 +168,13 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_4">
- <property name="title">
- <string>Dump</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <item>
- <widget class="QCheckBox" name="dump_decompressed_nso">
- <property name="whatsThis">
- <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
- </property>
- <property name="text">
- <string>Dump Decompressed NSOs</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="dump_exefs">
- <property name="whatsThis">
- <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
- </property>
- <property name="text">
- <string>Dump ExeFS</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index e636964e3..7c875ae87 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -37,6 +37,7 @@ void ConfigureDialog::ApplyConfiguration() {
ui->gameListTab->ApplyConfiguration();
ui->systemTab->ApplyConfiguration();
ui->profileManagerTab->ApplyConfiguration();
+ ui->filesystemTab->applyConfiguration();
ui->inputTab->ApplyConfiguration();
ui->hotkeysTab->ApplyConfiguration(registry);
ui->graphicsTab->ApplyConfiguration();
@@ -68,12 +69,14 @@ void ConfigureDialog::RetranslateUI() {
ui->tabWidget->setCurrentIndex(old_index);
}
+Q_DECLARE_METATYPE(QList<QWidget*>);
+
void ConfigureDialog::PopulateSelectionList() {
- const std::array<std::pair<QString, QStringList>, 4> items{
- {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
- {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
- {tr("Graphics"), {tr("Graphics")}},
- {tr("Controls"), {tr("Input"), tr("Hotkeys")}}},
+ const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
+ {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
+ {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}},
+ {tr("Graphics"), {ui->graphicsTab}},
+ {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
@@ -81,7 +84,7 @@ void ConfigureDialog::PopulateSelectionList() {
ui->selectorList->clear();
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
- item->setData(Qt::UserRole, entry.second);
+ item->setData(Qt::UserRole, QVariant::fromValue(entry.second));
ui->selectorList->addItem(item);
}
@@ -93,24 +96,27 @@ void ConfigureDialog::UpdateVisibleTabs() {
return;
}
- const std::map<QString, QWidget*> widgets = {
- {tr("General"), ui->generalTab},
- {tr("System"), ui->systemTab},
- {tr("Profiles"), ui->profileManagerTab},
- {tr("Input"), ui->inputTab},
- {tr("Hotkeys"), ui->hotkeysTab},
- {tr("Graphics"), ui->graphicsTab},
- {tr("Audio"), ui->audioTab},
- {tr("Debug"), ui->debugTab},
- {tr("Web"), ui->webTab},
- {tr("Game List"), ui->gameListTab},
+ const std::map<QWidget*, QString> widgets = {
+ {ui->generalTab, tr("General")},
+ {ui->systemTab, tr("System")},
+ {ui->profileManagerTab, tr("Profiles")},
+ {ui->inputTab, tr("Input")},
+ {ui->hotkeysTab, tr("Hotkeys")},
+ {ui->graphicsTab, tr("Graphics")},
+ {ui->audioTab, tr("Audio")},
+ {ui->debugTab, tr("Debug")},
+ {ui->webTab, tr("Web")},
+ {ui->gameListTab, tr("Game List")},
+ {ui->filesystemTab, tr("Filesystem")},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
ui->tabWidget->clear();
- const QStringList tabs = items[0]->data(Qt::UserRole).toStringList();
- for (const auto& tab : tabs) {
- ui->tabWidget->addTab(widgets.find(tab)->second, tab);
+
+ const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
+
+ for (const auto tab : tabs) {
+ ui->tabWidget->addTab(tab, widgets.at(tab));
}
}
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
new file mode 100644
index 000000000..29f540eb7
--- /dev/null
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -0,0 +1,177 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "core/settings.h"
+#include "ui_configure_filesystem.h"
+#include "yuzu/configuration/configure_filesystem.h"
+#include "yuzu/uisettings.h"
+
+namespace {
+
+template <typename T>
+void SetComboBoxFromData(QComboBox* combo_box, T data) {
+ const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data)));
+ if (index >= combo_box->count() || index < 0)
+ return;
+
+ combo_box->setCurrentIndex(index);
+}
+
+} // Anonymous namespace
+
+ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {
+ ui->setupUi(this);
+ this->setConfiguration();
+
+ connect(ui->nand_directory_button, &QToolButton::pressed, this,
+ [this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); });
+ connect(ui->sdmc_directory_button, &QToolButton::pressed, this,
+ [this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); });
+ connect(ui->gamecard_path_button, &QToolButton::pressed, this,
+ [this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); });
+ connect(ui->dump_path_button, &QToolButton::pressed, this,
+ [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });
+ connect(ui->load_path_button, &QToolButton::pressed, this,
+ [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); });
+ connect(ui->cache_directory_button, &QToolButton::pressed, this,
+ [this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); });
+
+ connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
+ &ConfigureFilesystem::ResetMetadata);
+
+ connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this,
+ &ConfigureFilesystem::UpdateEnabledControls);
+ connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this,
+ &ConfigureFilesystem::UpdateEnabledControls);
+}
+
+ConfigureFilesystem::~ConfigureFilesystem() = default;
+
+void ConfigureFilesystem::setConfiguration() {
+ ui->nand_directory_edit->setText(
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
+ ui->sdmc_directory_edit->setText(
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
+ ui->dump_path_edit->setText(
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
+ ui->load_path_edit->setText(
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
+ ui->cache_directory_edit->setText(
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
+
+ ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
+ ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
+ ui->dump_exefs->setChecked(Settings::values.dump_exefs);
+ ui->dump_nso->setChecked(Settings::values.dump_nso);
+
+ ui->cache_game_list->setChecked(UISettings::values.cache_game_list);
+
+ SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size);
+ SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size);
+ SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size);
+ SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size);
+
+ UpdateEnabledControls();
+}
+
+void ConfigureFilesystem::applyConfiguration() {
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
+ ui->nand_directory_edit->text().toStdString());
+ FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
+ ui->sdmc_directory_edit->text().toStdString());
+ FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString());
+ FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString());
+ FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
+ ui->cache_directory_edit->text().toStdString());
+ Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
+
+ Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
+ Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
+ Settings::values.dump_exefs = ui->dump_exefs->isChecked();
+ Settings::values.dump_nso = ui->dump_nso->isChecked();
+
+ UISettings::values.cache_game_list = ui->cache_game_list->isChecked();
+
+ Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
+ ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong());
+ Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
+ ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong());
+ Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
+ ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong());
+ Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
+ ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong());
+}
+
+void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
+ QString caption;
+
+ switch (target) {
+ case DirectoryTarget::NAND:
+ caption = tr("Select Emulated NAND Directory...");
+ break;
+ case DirectoryTarget::SD:
+ caption = tr("Select Emulated SD Directory...");
+ break;
+ case DirectoryTarget::Gamecard:
+ caption = tr("Select Gamecard Path...");
+ break;
+ case DirectoryTarget::Dump:
+ caption = tr("Select Dump Directory...");
+ break;
+ case DirectoryTarget::Load:
+ caption = tr("Select Mod Load Directory...");
+ break;
+ case DirectoryTarget::Cache:
+ caption = tr("Select Cache Directory...");
+ break;
+ }
+
+ QString str;
+ if (target == DirectoryTarget::Gamecard) {
+ str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(),
+ QStringLiteral("NX Gamecard;*.xci"));
+ } else {
+ str = QFileDialog::getExistingDirectory(this, caption, edit->text());
+ }
+
+ if (str.isEmpty())
+ return;
+
+ edit->setText(str);
+}
+
+void ConfigureFilesystem::ResetMetadata() {
+ if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
+ "game_list")) {
+ QMessageBox::information(this, tr("Reset Metadata Cache"),
+ tr("The metadata cache is already empty."));
+ } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
+ DIR_SEP + "game_list")) {
+ QMessageBox::information(this, tr("Reset Metadata Cache"),
+ tr("The operation completed successfully."));
+ UISettings::values.is_game_list_reload_pending.exchange(true);
+ } else {
+ QMessageBox::warning(
+ this, tr("Reset Metadata Cache"),
+ tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
+ }
+}
+
+void ConfigureFilesystem::UpdateEnabledControls() {
+ ui->gamecard_current_game->setEnabled(ui->gamecard_inserted->isChecked());
+ ui->gamecard_path_edit->setEnabled(ui->gamecard_inserted->isChecked() &&
+ !ui->gamecard_current_game->isChecked());
+ ui->gamecard_path_button->setEnabled(ui->gamecard_inserted->isChecked() &&
+ !ui->gamecard_current_game->isChecked());
+}
+
+void ConfigureFilesystem::retranslateUi() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h
new file mode 100644
index 000000000..a79303760
--- /dev/null
+++ b/src/yuzu/configuration/configure_filesystem.h
@@ -0,0 +1,43 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWidget>
+
+class QLineEdit;
+
+namespace Ui {
+class ConfigureFilesystem;
+}
+
+class ConfigureFilesystem : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureFilesystem(QWidget* parent = nullptr);
+ ~ConfigureFilesystem() override;
+
+ void applyConfiguration();
+ void retranslateUi();
+
+private:
+ void setConfiguration();
+
+ enum class DirectoryTarget {
+ NAND,
+ SD,
+ Gamecard,
+ Dump,
+ Load,
+ Cache,
+ };
+
+ void SetDirectory(DirectoryTarget target, QLineEdit* edit);
+ void ResetMetadata();
+ void UpdateEnabledControls();
+
+ std::unique_ptr<Ui::ConfigureFilesystem> ui;
+};
diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui
new file mode 100644
index 000000000..58cd07f52
--- /dev/null
+++ b/src/yuzu/configuration/configure_filesystem.ui
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureFilesystem</class>
+ <widget class="QWidget" name="ConfigureFilesystem">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>453</width>
+ <height>561</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Storage Directories</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>NAND</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QToolButton" name="nand_directory_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLineEdit" name="nand_directory_edit"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLineEdit" name="sdmc_directory_edit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>SD Card</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QToolButton" name="sdmc_directory_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Maximum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Gamecard</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Path</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLineEdit" name="gamecard_path_edit"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="gamecard_inserted">
+ <property name="text">
+ <string>Inserted</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="gamecard_current_game">
+ <property name="text">
+ <string>Current Game</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QToolButton" name="gamecard_path_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Storage Sizes</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>SD Card</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>System NAND</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="sysnand_size">
+ <item>
+ <property name="text">
+ <string>2.5 GB</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="sdmc_size">
+ <property name="currentText">
+ <string>32 GB</string>
+ </property>
+ <item>
+ <property name="text">
+ <string>1 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>2 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>4 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>8 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>16 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>32 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>64 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>128 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>256 GB</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>1 TB</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="usrnand_size">
+ <item>
+ <property name="text">
+ <string>26 GB</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>User NAND</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>NAND</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="nand_size">
+ <item>
+ <property name="text">
+ <string>29.1 GB</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Patch Manager</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="2">
+ <widget class="QLineEdit" name="load_path_edit"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLineEdit" name="dump_path_edit"/>
+ </item>
+ <item row="0" column="3">
+ <widget class="QToolButton" name="dump_path_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QToolButton" name="load_path_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="4">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="dump_nso">
+ <property name="text">
+ <string>Dump Decompressed NSOs</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="dump_exefs">
+ <property name="text">
+ <string>Dump ExeFS</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Mod Load Root</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Dump Root</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>Caching</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Cache Directory</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLineEdit" name="cache_directory_edit"/>
+ </item>
+ <item row="0" column="3">
+ <widget class="QToolButton" name="cache_directory_button">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="4">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="cache_game_list">
+ <property name="text">
+ <string>Cache Game List Metadata</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="reset_game_list_cache">
+ <property name="text">
+ <string>Reset Metadata Cache</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp
index d1724ba89..daedbc33e 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_gamelist.cpp
@@ -9,7 +9,7 @@
#include "core/settings.h"
#include "ui_configure_gamelist.h"
#include "yuzu/configuration/configure_gamelist.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
namespace {
constexpr std::array default_icon_sizes{
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 06d368dfc..98bc9b391 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -2,11 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <QCheckBox>
+#include <QSpinBox>
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_general.h"
#include "yuzu/configuration/configure_general.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
ConfigureGeneral::ConfigureGeneral(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGeneral) {
@@ -20,30 +22,29 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
SetConfiguration();
- connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this,
- [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
-
- ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+ connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
}
ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {
- ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
- ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
+
+ ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
+ ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
+ ui->frame_limit->setValue(Settings::values.frame_limit);
}
void ConfigureGeneral::ApplyConfiguration() {
- UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
UISettings::values.theme =
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
- Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
+ Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
+ Settings::values.frame_limit = ui->frame_limit->value();
}
void ConfigureGeneral::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 1a5721fe7..0bb91d64b 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -25,11 +25,31 @@
<item>
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
- <widget class="QCheckBox" name="toggle_deepscan">
- <property name="text">
- <string>Search sub-directories for games</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="toggle_frame_limit">
+ <property name="text">
+ <string>Limit Speed Percent</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="frame_limit">
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>9999</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
<widget class="QCheckBox" name="toggle_check_exit">
@@ -51,26 +71,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="PerformanceGroupBox">
- <property name="title">
- <string>Performance</string>
- </property>
- <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
- <item>
- <layout class="QVBoxLayout" name="PerformanceVerticalLayout">
- <item>
- <widget class="QCheckBox" name="use_cpu_jit">
- <property name="text">
- <string>Enable CPU JIT</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="theme_group_box">
<property name="title">
<string>Theme</string>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2b17b250c..2c9e322c9 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -55,7 +55,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
SetConfiguration();
- connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
const QColor new_bg_color = QColorDialog::getColor(bg_color);
if (!new_bg_color.isValid()) {
@@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() {
ui->resolution_factor_combobox->setCurrentIndex(
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
- ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
- ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
- ui->frame_limit->setValue(Settings::values.frame_limit);
ui->use_disk_shader_cache->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
@@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() {
void ConfigureGraphics::ApplyConfiguration() {
Settings::values.resolution_factor =
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
- Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
- Settings::values.frame_limit = ui->frame_limit->value();
Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.use_asynchronous_gpu_emulation =
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 15ab18ecd..0309ee300 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -23,33 +23,6 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QCheckBox" name="toggle_frame_limit">
- <property name="text">
- <string>Limit Speed Percent</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="frame_limit">
- <property name="suffix">
- <string>%</string>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>9999</number>
- </property>
- <property name="value">
- <number>100</number>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
<widget class="QCheckBox" name="use_disk_shader_cache">
<property name="text">
<string>Use disk shader cache</string>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 4dd775aab..f2977719c 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -79,7 +79,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
LoadConfiguration();
UpdateUIEnabled();
- connect(ui->restore_defaults_button, &QPushButton::pressed, this,
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
&ConfigureInput::RestoreDefaults);
for (auto* enabled : players_controller) {
@@ -96,20 +96,20 @@ ConfigureInput::ConfigureInput(QWidget* parent)
&ConfigureInput::UpdateUIEnabled);
for (std::size_t i = 0; i < players_configure.size(); ++i) {
- connect(players_configure[i], &QPushButton::pressed, this,
+ connect(players_configure[i], &QPushButton::clicked, this,
[this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); });
}
- connect(ui->handheld_configure, &QPushButton::pressed, this,
+ connect(ui->handheld_configure, &QPushButton::clicked, this,
[this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); });
- connect(ui->debug_configure, &QPushButton::pressed, this,
+ connect(ui->debug_configure, &QPushButton::clicked, this,
[this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); });
- connect(ui->mouse_advanced, &QPushButton::pressed, this,
+ connect(ui->mouse_advanced, &QPushButton::clicked, this,
[this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); });
- connect(ui->touchscreen_advanced, &QPushButton::pressed, this,
+ connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
[this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
}
@@ -182,6 +182,8 @@ void ConfigureInput::UpdateUIEnabled() {
players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);
}
+ ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() &&
+ !ui->use_docked_mode->isChecked());
ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked());
ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() &&
!ui->use_docked_mode->isChecked());
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 8378451c8..efffd8487 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>ConfigureInput</string>
+ <string>Custom Input Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 916baccc1..a968cfb5d 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -244,7 +244,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button, &QPushButton::released, [=] {
+ connect(button, &QPushButton::clicked, [=] {
HandleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
@@ -273,7 +273,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(analog_button, &QPushButton::released, [=]() {
+ connect(analog_button, &QPushButton::clicked, [=]() {
HandleClick(analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
@@ -300,19 +300,22 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
menu_location));
});
}
- connect(analog_map_stick[analog_id], &QPushButton::released, [=] {
- QMessageBox::information(this, tr("Information"),
- tr("After pressing OK, first move your joystick horizontally, "
- "and then vertically."));
- HandleClick(
- analog_map_stick[analog_id],
- [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
- InputCommon::Polling::DeviceType::Analog);
+ connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] {
+ if (QMessageBox::information(
+ this, tr("Information"),
+ tr("After pressing OK, first move your joystick horizontally, "
+ "and then vertically."),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
+ HandleClick(
+ analog_map_stick[analog_id],
+ [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
+ InputCommon::Polling::DeviceType::Analog);
+ }
});
}
- connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
- connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });
+ connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
+ connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp
index 864803ea3..ab3a11d30 100644
--- a/src/yuzu/configuration/configure_input_simple.cpp
+++ b/src/yuzu/configuration/configure_input_simple.cpp
@@ -9,7 +9,7 @@
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_simple.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
namespace {
@@ -101,7 +101,7 @@ ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureInputSimple::OnSelectProfile);
- connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure);
+ connect(ui->profile_configure, &QPushButton::clicked, this, &ConfigureInputSimple::OnConfigure);
LoadConfiguration();
}
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index b7305e653..0a4abe34f 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -83,7 +83,7 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
}
button->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(button, &QPushButton::released, [=] {
+ connect(button, &QPushButton::clicked, [=] {
HandleClick(
button_map[button_id],
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
@@ -104,8 +104,8 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)
});
}
- connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
- connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });
+ connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
+ connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
index 90336e235..d7f259f12 100644
--- a/src/yuzu/configuration/configure_per_general.cpp
+++ b/src/yuzu/configuration/configure_per_general.cpp
@@ -23,7 +23,7 @@
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_general.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index c90f4cdd8..f53423440 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -108,10 +108,10 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser);
- connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser);
- connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser);
- connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser);
- connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage);
+ connect(ui->pm_add, &QPushButton::clicked, this, &ConfigureProfileManager::AddUser);
+ connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser);
+ connect(ui->pm_remove, &QPushButton::clicked, this, &ConfigureProfileManager::DeleteUser);
+ connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage);
scene = new QGraphicsScene;
ui->current_user_icon->setScene(scene);
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 8ced28c75..7d7cc00b7 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -11,7 +11,7 @@ ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) {
ui->setupUi(this);
- connect(ui->restore_defaults_button, &QPushButton::pressed, this,
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
&ConfigureTouchscreenAdvanced::RestoreDefaults);
LoadConfiguration();
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index 5a70ef168..336b062b3 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -9,7 +9,7 @@
#include "core/telemetry_session.h"
#include "ui_configure_web.h"
#include "yuzu/configuration/configure_web.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
ConfigureWeb::ConfigureWeb(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp
index 320898f6a..207ff4d58 100644
--- a/src/yuzu/debugger/console.cpp
+++ b/src/yuzu/debugger/console.cpp
@@ -10,7 +10,7 @@
#include "common/logging/backend.h"
#include "yuzu/debugger/console.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
namespace Debugger {
void ToggleConsole() {
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
index 9d87a41eb..ea0079353 100644
--- a/src/yuzu/discord_impl.cpp
+++ b/src/yuzu/discord_impl.cpp
@@ -9,7 +9,7 @@
#include "core/core.h"
#include "core/loader/loader.h"
#include "yuzu/discord_impl.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
namespace DiscordRPC {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 83d675773..d5fab2f1f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -23,7 +23,7 @@
#include "yuzu/game_list_p.h"
#include "yuzu/game_list_worker.h"
#include "yuzu/main.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {}
@@ -34,7 +34,6 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
return QObject::eventFilter(obj, event);
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
- int rowCount = gamelist->tree_view->model()->rowCount();
QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower();
// If the searchfield's text hasn't changed special function keys get checked
@@ -56,19 +55,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
// If there is only one result launch this game
case Qt::Key_Return:
case Qt::Key_Enter: {
- QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view);
- QModelIndex root_index = item_model->invisibleRootItem()->index();
- QStandardItem* child_file;
- QString file_path;
- int resultCount = 0;
- for (int i = 0; i < rowCount; ++i) {
- if (!gamelist->tree_view->isRowHidden(i, root_index)) {
- ++resultCount;
- child_file = gamelist->item_model->item(i, 0);
- file_path = child_file->data(GameListItemPath::FullPathRole).toString();
- }
- }
- if (resultCount == 1) {
+ if (gamelist->search_field->visible == 1) {
+ QString file_path = gamelist->getLastFilterResultItem();
+
// To avoid loading error dialog loops while confirming them using enter
// Also users usually want to run a different game after closing one
gamelist->search_field->edit_filter->clear();
@@ -88,9 +77,31 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
}
void GameListSearchField::setFilterResult(int visible, int total) {
+ this->visible = visible;
+ this->total = total;
+
label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
}
+QString GameList::getLastFilterResultItem() const {
+ QStandardItem* folder;
+ QStandardItem* child;
+ QString file_path;
+ const int folder_count = item_model->rowCount();
+ for (int i = 0; i < folder_count; ++i) {
+ folder = item_model->item(i, 0);
+ const QModelIndex folder_index = folder->index();
+ const int children_count = folder->rowCount();
+ for (int j = 0; j < children_count; ++j) {
+ if (!tree_view->isRowHidden(j, folder_index)) {
+ child = folder->child(j, 0);
+ file_path = child->data(GameListItemPath::FullPathRole).toString();
+ }
+ }
+ }
+ return file_path;
+}
+
void GameListSearchField::clear() {
edit_filter->clear();
}
@@ -147,45 +158,120 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput)
[&haystack](const QString& s) { return haystack.contains(s); });
}
+// Syncs the expanded state of Game Directories with settings to persist across sessions
+void GameList::onItemExpanded(const QModelIndex& item) {
+ const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>();
+ if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
+ type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir)
+ item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded =
+ tree_view->isExpanded(item);
+}
+
// Event in order to filter the gamelist after editing the searchfield
void GameList::onTextChanged(const QString& new_text) {
- const int row_count = tree_view->model()->rowCount();
- const QString edit_filter_text = new_text.toLower();
- const QModelIndex root_index = item_model->invisibleRootItem()->index();
+ const int folder_count = tree_view->model()->rowCount();
+ QString edit_filter_text = new_text.toLower();
+ QStandardItem* folder;
+ QStandardItem* child;
+ int children_total = 0;
+ QModelIndex root_index = item_model->invisibleRootItem()->index();
// If the searchfield is empty every item is visible
// Otherwise the filter gets applied
if (edit_filter_text.isEmpty()) {
- for (int i = 0; i < row_count; ++i) {
- tree_view->setRowHidden(i, root_index, false);
+ for (int i = 0; i < folder_count; ++i) {
+ folder = item_model->item(i, 0);
+ const QModelIndex folder_index = folder->index();
+ const int children_count = folder->rowCount();
+ for (int j = 0; j < children_count; ++j) {
+ ++children_total;
+ tree_view->setRowHidden(j, folder_index, false);
+ }
}
- search_field->setFilterResult(row_count, row_count);
+ search_field->setFilterResult(children_total, children_total);
} else {
int result_count = 0;
- for (int i = 0; i < row_count; ++i) {
- const QStandardItem* child_file = item_model->item(i, 0);
- const QString file_path =
- child_file->data(GameListItemPath::FullPathRole).toString().toLower();
- const QString file_title =
- child_file->data(GameListItemPath::TitleRole).toString().toLower();
- const QString file_program_id =
- child_file->data(GameListItemPath::ProgramIdRole).toString().toLower();
-
- // Only items which filename in combination with its title contains all words
- // that are in the searchfield will be visible in the gamelist
- // The search is case insensitive because of toLower()
- // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
- // multiple conversions of edit_filter_text for each game in the gamelist
- const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) +
- QLatin1Char{' '} + file_title;
- if (ContainsAllWords(file_name, edit_filter_text) ||
- (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
- tree_view->setRowHidden(i, root_index, false);
- ++result_count;
- } else {
- tree_view->setRowHidden(i, root_index, true);
+ for (int i = 0; i < folder_count; ++i) {
+ folder = item_model->item(i, 0);
+ const QModelIndex folder_index = folder->index();
+ const int children_count = folder->rowCount();
+ for (int j = 0; j < children_count; ++j) {
+ ++children_total;
+ const QStandardItem* child = folder->child(j, 0);
+ 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 =
+ child->data(GameListItemPath::ProgramIdRole).toString().toLower();
+
+ // Only items which filename in combination with its title contains all words
+ // that are in the searchfield will be visible in the gamelist
+ // The search is case insensitive because of toLower()
+ // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
+ // multiple conversions of edit_filter_text for each game in the gamelist
+ const QString file_name =
+ file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
+ file_title;
+ if (ContainsAllWords(file_name, edit_filter_text) ||
+ (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
+ tree_view->setRowHidden(j, folder_index, false);
+ ++result_count;
+ } else {
+ tree_view->setRowHidden(j, folder_index, true);
+ }
+ search_field->setFilterResult(result_count, children_total);
}
- search_field->setFilterResult(result_count, row_count);
+ }
+ }
+}
+
+void GameList::onUpdateThemedIcons() {
+ for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
+ QStandardItem* child = item_model->invisibleRootItem()->child(i);
+
+ const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
+ 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:
+ child->setData(
+ QIcon::fromTheme(QStringLiteral("chip"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ break;
+ 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 =
+ child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
+ const QString icon_name = QFileInfo::exists(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("plus"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ break;
}
}
}
@@ -214,7 +300,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
- tree_view->setUniformRowHeights(true);
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
@@ -230,12 +315,16 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type"));
item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size"));
}
+ item_model->setSortRole(GameListItemPath::TitleRole);
+ connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons);
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
+ connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded);
+ connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded);
- // We must register all custom types with the Qt Automoc system so that we are able to use it
- // with signals/slots. In this case, QList falls under the umbrells of custom types.
+ // We must register all custom types with the Qt Automoc system so that we are able to use
+ // it with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
@@ -263,38 +352,68 @@ void GameList::clearFilter() {
search_field->clear();
}
-void GameList::AddEntry(const QList<QStandardItem*>& entry_items) {
+void GameList::AddDirEntry(GameListDir* entry_items) {
item_model->invisibleRootItem()->appendRow(entry_items);
+ tree_view->setExpanded(
+ entry_items->index(),
+ entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded);
}
-void GameList::ValidateEntry(const QModelIndex& item) {
- // We don't care about the individual QStandardItem that was selected, but its row.
- const int row = item_model->itemFromIndex(item)->row();
- const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
- const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString();
-
- if (file_path.isEmpty())
- return;
-
- if (!QFileInfo::exists(file_path))
- return;
+void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) {
+ parent->appendRow(entry_items);
+}
- const QFileInfo file_info{file_path};
- if (file_info.isDir()) {
- const QDir dir{file_path};
- const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
- if (matching_main.size() == 1) {
- emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);
+void GameList::ValidateEntry(const QModelIndex& item) {
+ const auto selected = item.sibling(item.row(), 0);
+
+ switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
+ case GameListItemType::Game: {
+ const QString file_path = selected.data(GameListItemPath::FullPathRole).toString();
+ if (file_path.isEmpty())
+ return;
+ const QFileInfo file_info(file_path);
+ if (!file_info.exists())
+ return;
+
+ if (file_info.isDir()) {
+ const QDir dir{file_path};
+ const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
+ if (matching_main.size() == 1) {
+ emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);
+ }
+ return;
}
- return;
+
+ // Users usually want to run a different game after closing one
+ search_field->clear();
+ emit GameChosen(file_path);
+ break;
+ }
+ case GameListItemType::AddDir:
+ emit AddDirectory();
+ break;
}
+}
- // Users usually want to run a diffrent game after closing one
- search_field->clear();
- emit GameChosen(file_path);
+bool GameList::isEmpty() const {
+ for (int i = 0; i < item_model->rowCount(); i++) {
+ const QStandardItem* child = item_model->invisibleRootItem()->child(i);
+ const auto type = static_cast<GameListItemType>(child->type());
+ if (!child->hasChildren() &&
+ (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir ||
+ type == GameListItemType::SysNandDir)) {
+ item_model->invisibleRootItem()->removeRow(child->row());
+ i--;
+ };
+ }
+ return !item_model->invisibleRootItem()->hasChildren();
}
void GameList::DonePopulating(QStringList watch_list) {
+ emit ShowList(!isEmpty());
+
+ item_model->invisibleRootItem()->appendRow(new GameListAddDir());
+
// Clear out the old directories to watch for changes and add the new ones
auto watch_dirs = watcher->directories();
if (!watch_dirs.isEmpty()) {
@@ -311,9 +430,13 @@ void GameList::DonePopulating(QStringList watch_list) {
QCoreApplication::processEvents();
}
tree_view->setEnabled(true);
- int rowCount = tree_view->model()->rowCount();
- search_field->setFilterResult(rowCount, rowCount);
- if (rowCount > 0) {
+ const int folder_count = tree_view->model()->rowCount();
+ int children_total = 0;
+ for (int i = 0; i < folder_count; ++i) {
+ children_total += item_model->item(i, 0)->rowCount();
+ }
+ search_field->setFilterResult(children_total, children_total);
+ if (children_total > 0) {
search_field->setFocus();
}
}
@@ -323,12 +446,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
if (!item.isValid())
return;
- int row = item_model->itemFromIndex(item)->row();
- QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
- u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
- std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
-
+ const auto selected = item.sibling(item.row(), 0);
QMenu context_menu;
+ switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
+ case GameListItemType::Game:
+ AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
+ selected.data(GameListItemPath::FullPathRole).toString().toStdString());
+ break;
+ case GameListItemType::CustomDir:
+ AddPermDirPopup(context_menu, selected);
+ AddCustomDirPopup(context_menu, selected);
+ break;
+ case GameListItemType::SdmcDir:
+ case GameListItemType::UserNandDir:
+ case GameListItemType::SysNandDir:
+ AddPermDirPopup(context_menu, selected);
+ break;
+ }
+ context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
+}
+
+void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) {
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
QAction* open_transferable_shader_cache =
@@ -344,19 +482,86 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
- connect(open_save_location, &QAction::triggered,
- [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
- connect(open_lfs_location, &QAction::triggered,
- [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
+ connect(open_save_location, &QAction::triggered, [this, program_id]() {
+ emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData);
+ });
+ connect(open_lfs_location, &QAction::triggered, [this, program_id]() {
+ emit OpenFolderRequested(program_id, GameListOpenTarget::ModData);
+ });
connect(open_transferable_shader_cache, &QAction::triggered,
- [&]() { emit OpenTransferableShaderCacheRequested(program_id); });
- connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
- connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
- connect(navigate_to_gamedb_entry, &QAction::triggered,
- [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
- connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });
+ [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); });
+ connect(dump_romfs, &QAction::triggered,
+ [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); });
+ connect(copy_tid, &QAction::triggered,
+ [this, program_id]() { emit CopyTIDRequested(program_id); });
+ connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
+ emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
+ });
+ connect(properties, &QAction::triggered,
+ [this, path]() { emit OpenPerGameGeneralRequested(path); });
+};
- context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
+void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
+ UISettings::GameDir& game_dir =
+ *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
+
+ QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders"));
+ QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory"));
+
+ deep_scan->setCheckable(true);
+ deep_scan->setChecked(game_dir.deep_scan);
+
+ connect(deep_scan, &QAction::triggered, [this, &game_dir] {
+ game_dir.deep_scan = !game_dir.deep_scan;
+ PopulateAsync(UISettings::values.game_dirs);
+ });
+ connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] {
+ UISettings::values.game_dirs.removeOne(game_dir);
+ item_model->invisibleRootItem()->removeRow(selected.row());
+ });
+}
+
+void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
+ UISettings::GameDir& game_dir =
+ *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
+
+ QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up"));
+ QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down "));
+ QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location"));
+
+ const int row = selected.row();
+
+ move_up->setEnabled(row > 0);
+ move_down->setEnabled(row < item_model->rowCount() - 2);
+
+ connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] {
+ // find the indices of the items in settings and swap them
+ std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
+ UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
+ *selected.sibling(row - 1, 0)
+ .data(GameListDir::GameDirRole)
+ .value<UISettings::GameDir*>())]);
+ // move the treeview items
+ QList<QStandardItem*> item = item_model->takeRow(row);
+ item_model->invisibleRootItem()->insertRow(row - 1, item);
+ tree_view->setExpanded(selected, game_dir.expanded);
+ });
+
+ connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] {
+ // find the indices of the items in settings and swap them
+ std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
+ UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
+ *selected.sibling(row + 1, 0)
+ .data(GameListDir::GameDirRole)
+ .value<UISettings::GameDir*>())]);
+ // move the treeview items
+ const QList<QStandardItem*> item = item_model->takeRow(row);
+ item_model->invisibleRootItem()->insertRow(row + 1, item);
+ tree_view->setExpanded(selected, game_dir.expanded);
+ });
+
+ connect(open_directory_location, &QAction::triggered,
+ [this, game_dir] { emit OpenDirectory(game_dir.path); });
}
void GameList::LoadCompatibilityList() {
@@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() {
}
}
-void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
- const QFileInfo dir_info{dir_path};
- if (!dir_info.exists() || !dir_info.isDir()) {
- LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString());
- search_field->setFilterResult(0, 0);
- return;
- }
-
+void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setEnabled(false);
// Update the columns in case UISettings has changed
@@ -433,17 +631,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
+ search_field->clear();
emit ShouldCancelWorker();
- GameListWorker* worker =
- new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
+ GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
+ connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
+ Qt::QueuedConnection);
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
Qt::QueuedConnection);
- // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel
- // without delay.
+ // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
+ // cancel without delay.
connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
Qt::DirectConnection);
@@ -468,14 +668,43 @@ void GameList::LoadInterfaceLayout() {
const QStringList GameList::supported_file_extensions = {
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
- QStringLiteral("xci"), QStringLiteral("nsp"),
-};
+ QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
void GameList::RefreshGameDirectory() {
- if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
+ if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
- search_field->clear();
- PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ PopulateAsync(UISettings::values.game_dirs);
}
}
+
+GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
+ connect(parent, &GMainWindow::UpdateThemedIcons, this,
+ &GameListPlaceholder::onUpdateThemedIcons);
+
+ layout = new QVBoxLayout;
+ image = new QLabel;
+ text = new QLabel;
+ layout->setAlignment(Qt::AlignCenter);
+ image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
+
+ text->setText(tr("Double-click to add a new folder to the game list"));
+ QFont font = text->font();
+ font.setPointSize(20);
+ text->setFont(font);
+ text->setAlignment(Qt::AlignHCenter);
+ image->setAlignment(Qt::AlignHCenter);
+
+ layout->addWidget(image);
+ layout->addWidget(text);
+ setLayout(layout);
+}
+
+GameListPlaceholder::~GameListPlaceholder() = default;
+
+void GameListPlaceholder::onUpdateThemedIcons() {
+ image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
+}
+
+void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
+ emit GameListPlaceholder::AddDirectory();
+}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index f8f8bd6c5..878d94413 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -8,6 +8,7 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
+#include <QList>
#include <QModelIndex>
#include <QSettings>
#include <QStandardItem>
@@ -16,13 +17,16 @@
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
+#include <QVector>
#include <QWidget>
#include "common/common_types.h"
+#include "uisettings.h"
#include "yuzu/compatibility_list.h"
class GameListWorker;
class GameListSearchField;
+class GameListDir;
class GMainWindow;
namespace FileSys {
@@ -52,12 +56,14 @@ public:
FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
~GameList() override;
+ QString getLastFilterResultItem() const;
void clearFilter();
void setFilterFocus();
void setFilterVisible(bool visibility);
+ bool isEmpty() const;
void LoadCompatibilityList();
- void PopulateAsync(const QString& dir_path, bool deep_scan);
+ void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
void SaveInterfaceLayout();
void LoadInterfaceLayout();
@@ -74,19 +80,29 @@ signals:
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
void OpenPerGameGeneralRequested(const std::string& file);
+ void OpenDirectory(const QString& directory);
+ void AddDirectory();
+ void ShowList(bool show);
private slots:
+ void onItemExpanded(const QModelIndex& item);
void onTextChanged(const QString& new_text);
void onFilterCloseClicked();
+ void onUpdateThemedIcons();
private:
- void AddEntry(const QList<QStandardItem*>& entry_items);
+ void AddDirEntry(GameListDir* entry_items);
+ void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
void ValidateEntry(const QModelIndex& item);
void DonePopulating(QStringList watch_list);
- void PopupContextMenu(const QPoint& menu_location);
void RefreshGameDirectory();
+ void PopupContextMenu(const QPoint& menu_location);
+ void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path);
+ void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
+ void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
+
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;
GameListSearchField* search_field;
@@ -102,3 +118,24 @@ private:
};
Q_DECLARE_METATYPE(GameListOpenTarget);
+
+class GameListPlaceholder : public QWidget {
+ Q_OBJECT
+public:
+ explicit GameListPlaceholder(GMainWindow* parent = nullptr);
+ ~GameListPlaceholder();
+
+signals:
+ void AddDirectory();
+
+private slots:
+ void onUpdateThemedIcons();
+
+protected:
+ void mouseDoubleClickEvent(QMouseEvent* event) override;
+
+private:
+ QVBoxLayout* layout = nullptr;
+ QLabel* image = nullptr;
+ QLabel* text = nullptr;
+};
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 0b458ef48..a8d888fee 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -10,6 +10,7 @@
#include <utility>
#include <QCoreApplication>
+#include <QFileInfo>
#include <QImage>
#include <QObject>
#include <QStandardItem>
@@ -19,9 +20,20 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
+enum class GameListItemType {
+ Game = QStandardItem::UserType + 1,
+ CustomDir = QStandardItem::UserType + 2,
+ SdmcDir = QStandardItem::UserType + 3,
+ UserNandDir = QStandardItem::UserType + 4,
+ SysNandDir = QStandardItem::UserType + 5,
+ AddDir = QStandardItem::UserType + 6
+};
+
+Q_DECLARE_METATYPE(GameListItemType);
+
/**
* Gets the default icon (for games without valid title metadata)
* @param size The desired width and height of the default icon.
@@ -36,8 +48,13 @@ static QPixmap GetDefaultIcon(u32 size) {
class GameListItem : public QStandardItem {
public:
+ // used to access type from item index
+ static const int TypeRole = Qt::UserRole + 1;
+ static const int SortRole = Qt::UserRole + 2;
GameListItem() = default;
- explicit GameListItem(const QString& string) : QStandardItem(string) {}
+ GameListItem(const QString& string) : QStandardItem(string) {
+ setData(string, SortRole);
+ }
};
/**
@@ -48,14 +65,15 @@ public:
*/
class GameListItemPath : public GameListItem {
public:
- static const int FullPathRole = Qt::UserRole + 1;
- static const int TitleRole = Qt::UserRole + 2;
- static const int ProgramIdRole = Qt::UserRole + 3;
- static const int FileTypeRole = Qt::UserRole + 4;
+ static const int TitleRole = SortRole;
+ static const int FullPathRole = SortRole + 1;
+ static const int ProgramIdRole = SortRole + 2;
+ static const int FileTypeRole = SortRole + 3;
GameListItemPath() = default;
GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
const QString& game_name, const QString& game_type, u64 program_id) {
+ setData(type(), TypeRole);
setData(game_path, FullPathRole);
setData(game_name, TitleRole);
setData(qulonglong(program_id), ProgramIdRole);
@@ -72,6 +90,10 @@ public:
setData(picture, Qt::DecorationRole);
}
+ int type() const override {
+ return static_cast<int>(GameListItemType::Game);
+ }
+
QVariant data(int role) const override {
if (role == Qt::DisplayRole) {
std::string filename;
@@ -103,9 +125,11 @@ public:
class GameListItemCompat : public GameListItem {
Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
public:
- static const int CompatNumberRole = Qt::UserRole + 1;
+ static const int CompatNumberRole = SortRole;
GameListItemCompat() = default;
explicit GameListItemCompat(const QString& compatibility) {
+ setData(type(), TypeRole);
+
struct CompatStatus {
QString color;
const char* text;
@@ -135,6 +159,10 @@ public:
setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
}
+ int type() const override {
+ return static_cast<int>(GameListItemType::Game);
+ }
+
bool operator<(const QStandardItem& other) const override {
return data(CompatNumberRole) < other.data(CompatNumberRole);
}
@@ -146,12 +174,12 @@ public:
* human-readable string representation will be displayed to the user.
*/
class GameListItemSize : public GameListItem {
-
public:
- static const int SizeRole = Qt::UserRole + 1;
+ static const int SizeRole = SortRole;
GameListItemSize() = default;
explicit GameListItemSize(const qulonglong size_bytes) {
+ setData(type(), TypeRole);
setData(size_bytes, SizeRole);
}
@@ -167,6 +195,10 @@ public:
}
}
+ int type() const override {
+ return static_cast<int>(GameListItemType::Game);
+ }
+
/**
* This operator is, in practice, only used by the TreeView sorting systems.
* Override it so that it will correctly sort by numerical value instead of by string
@@ -177,6 +209,82 @@ public:
}
};
+class GameListDir : public GameListItem {
+public:
+ static const int GameDirRole = Qt::UserRole + 2;
+
+ explicit GameListDir(UISettings::GameDir& directory,
+ GameListItemType dir_type = GameListItemType::CustomDir)
+ : dir_type{dir_type} {
+ setData(type(), TypeRole);
+
+ UISettings::GameDir* game_dir = &directory;
+ setData(QVariant::fromValue(game_dir), GameDirRole);
+
+ const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
+ switch (dir_type) {
+ case GameListItemType::SdmcDir:
+ setData(
+ 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);
+ break;
+ case GameListItemType::UserNandDir:
+ setData(
+ 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);
+ break;
+ case GameListItemType::SysNandDir:
+ setData(
+ 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);
+ break;
+ case GameListItemType::CustomDir:
+ const QString icon_name = QFileInfo::exists(game_dir->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(game_dir->path, Qt::DisplayRole);
+ break;
+ };
+ };
+
+ int type() const override {
+ return static_cast<int>(dir_type);
+ }
+
+private:
+ GameListItemType dir_type;
+};
+
+class GameListAddDir : public GameListItem {
+public:
+ explicit GameListAddDir() {
+ setData(type(), TypeRole);
+
+ const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
+ setData(QIcon::fromTheme(QStringLiteral("plus"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
+ }
+
+ int type() const override {
+ return static_cast<int>(GameListItemType::AddDir);
+ }
+};
+
class GameList;
class QHBoxLayout;
class QTreeView;
@@ -208,6 +316,9 @@ private:
// EventFilter in order to process systemkeys while editing the searchfield
bool eventFilter(QObject* obj, QEvent* event) override;
};
+ int visible;
+ int total;
+
QHBoxLayout* layout_filter = nullptr;
QTreeView* tree_view = nullptr;
QLabel* label_filter = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 4f30e9147..fd21a9761 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -29,7 +29,7 @@
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
#include "yuzu/game_list_worker.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
namespace {
@@ -223,21 +223,37 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
} // Anonymous namespace
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
- FileSys::ManualContentProvider* provider, QString dir_path,
- bool deep_scan, const CompatibilityList& compatibility_list)
- : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
+ FileSys::ManualContentProvider* provider,
+ QVector<UISettings::GameDir>& game_dirs,
+ const CompatibilityList& compatibility_list)
+ : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs),
compatibility_list(compatibility_list) {}
GameListWorker::~GameListWorker() = default;
-void GameListWorker::AddTitlesToGameList() {
- const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
- Core::System::GetInstance().GetContentProvider());
- const auto installed_games = cache.ListEntriesFilterOrigin(
- std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
+void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
+ using namespace FileSys;
+
+ const auto& cache =
+ dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider());
+
+ std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games;
+ installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
+ ContentRecordType::Program);
+
+ if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) {
+ installed_games = cache.ListEntriesFilterOrigin(
+ ContentProviderUnionSlot::SDMC, TitleType::Application, ContentRecordType::Program);
+ } else if (parent_dir->type() == static_cast<int>(GameListItemType::UserNandDir)) {
+ installed_games = cache.ListEntriesFilterOrigin(
+ ContentProviderUnionSlot::UserNAND, TitleType::Application, ContentRecordType::Program);
+ } else if (parent_dir->type() == static_cast<int>(GameListItemType::SysNandDir)) {
+ installed_games = cache.ListEntriesFilterOrigin(
+ ContentProviderUnionSlot::SysNAND, TitleType::Application, ContentRecordType::Program);
+ }
for (const auto& [slot, game] : installed_games) {
- if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
+ if (slot == ContentProviderUnionSlot::FrontendManual)
continue;
const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
@@ -250,21 +266,22 @@ void GameListWorker::AddTitlesToGameList() {
u64 program_id = 0;
loader->ReadProgramId(program_id);
- const FileSys::PatchManager patch{program_id};
- const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control);
+ const PatchManager patch{program_id};
+ const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control);
if (control != nullptr)
GetMetadataFromControlNCA(patch, *control, icon, name);
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
- compatibility_list, patch));
+ compatibility_list, patch),
+ parent_dir);
}
}
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
- unsigned int recursion) {
- const auto callback = [this, target, recursion](u64* num_entries_out,
- const std::string& directory,
- const std::string& virtual_name) -> bool {
+ unsigned int recursion, GameListDir* parent_dir) {
+ const auto callback = [this, target, recursion,
+ parent_dir](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
if (stop_processing) {
// Breaks the callback loop.
return false;
@@ -317,11 +334,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{program_id};
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
- compatibility_list, patch));
+ compatibility_list, patch),
+ parent_dir);
}
} else if (is_dir && recursion > 0) {
watch_list.append(QString::fromStdString(physical_name));
- ScanFileSystem(target, physical_name, recursion - 1);
+ ScanFileSystem(target, physical_name, recursion - 1, parent_dir);
}
return true;
@@ -332,12 +350,32 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
void GameListWorker::run() {
stop_processing = false;
- watch_list.append(dir_path);
- provider->ClearAllEntries();
- ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
- deep_scan ? 256 : 0);
- AddTitlesToGameList();
- ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
+
+ for (UISettings::GameDir& game_dir : game_dirs) {
+ if (game_dir.path == QStringLiteral("SDMC")) {
+ auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
+ emit DirEntryReady({game_list_dir});
+ AddTitlesToGameList(game_list_dir);
+ } else if (game_dir.path == QStringLiteral("UserNAND")) {
+ auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
+ emit DirEntryReady({game_list_dir});
+ AddTitlesToGameList(game_list_dir);
+ } else if (game_dir.path == QStringLiteral("SysNAND")) {
+ auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
+ emit DirEntryReady({game_list_dir});
+ AddTitlesToGameList(game_list_dir);
+ } else {
+ watch_list.append(game_dir.path);
+ auto* const game_list_dir = new GameListDir(game_dir);
+ emit DirEntryReady({game_list_dir});
+ provider->ClearAllEntries();
+ ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
+ game_list_dir);
+ ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
+ game_dir.deep_scan ? 256 : 0, game_list_dir);
+ }
+ };
+
emit Finished(watch_list);
}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 7c3074af9..6e52fca89 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -14,6 +14,7 @@
#include <QObject>
#include <QRunnable>
#include <QString>
+#include <QVector>
#include "common/common_types.h"
#include "yuzu/compatibility_list.h"
@@ -33,9 +34,10 @@ class GameListWorker : public QObject, public QRunnable {
Q_OBJECT
public:
- GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
- FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
- const CompatibilityList& compatibility_list);
+ explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
+ FileSys::ManualContentProvider* provider,
+ QVector<UISettings::GameDir>& game_dirs,
+ const CompatibilityList& compatibility_list);
~GameListWorker() override;
/// Starts the processing of directory tree information.
@@ -48,31 +50,33 @@ signals:
/**
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
* to be added to the game list.
- * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
+ * @param entry_items a list with `QStandardItem`s that make up the columns of the new
+ * entry.
*/
- void EntryReady(QList<QStandardItem*> entry_items);
+ void DirEntryReady(GameListDir* entry_items);
+ void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
/**
- * After the worker has traversed the game directory looking for entries, this signal is emitted
- * with a list of folders that should be watched for changes as well.
+ * After the worker has traversed the game directory looking for entries, this signal is
+ * emitted with a list of folders that should be watched for changes as well.
*/
void Finished(QStringList watch_list);
private:
- void AddTitlesToGameList();
+ void AddTitlesToGameList(GameListDir* parent_dir);
enum class ScanTarget {
FillManualContentProvider,
PopulateGameList,
};
- void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
+ void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion,
+ GameListDir* parent_dir);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;
QStringList watch_list;
- QString dir_path;
- bool deep_scan;
const CompatibilityList& compatibility_list;
+ QVector<UISettings::GameDir>& game_dirs;
std::atomic_bool stop_processing;
};
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 4582e7f21..d4e97fa16 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -7,7 +7,7 @@
#include <QTreeWidgetItem>
#include <QtGlobal>
#include "yuzu/hotkeys.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index cd32623b4..2d82df739 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -6,6 +6,9 @@
#include <clocale>
#include <memory>
#include <thread>
+#ifdef __APPLE__
+#include <unistd.h> // for chdir
+#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/error.h"
@@ -19,6 +22,8 @@
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/scope_acquire_window_context.h"
#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/am/applet_ae.h"
+#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
@@ -51,6 +56,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QProgressDialog>
#include <QShortcut>
#include <QStatusBar>
+#include <QSysInfo>
#include <QtConcurrent/QtConcurrent>
#include <fmt/format.h>
@@ -63,6 +69,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
+#ifdef ARCHITECTURE_x86_64
+#include "common/x64/cpu_detect.h"
+#endif
#include "common/telemetry.h"
#include "core/core.h"
#include "core/crypto/key_manager.h"
@@ -76,6 +85,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/submission_package.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
@@ -100,7 +110,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/hotkeys.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
-#include "yuzu/ui_settings.h"
+#include "yuzu/uisettings.h"
#ifdef USE_DISCORD_PRESENCE
#include "yuzu/discord_impl.h"
@@ -119,6 +129,7 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
#ifdef _WIN32
+#include <windows.h>
extern "C" {
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
// graphics
@@ -201,6 +212,10 @@ GMainWindow::GMainWindow()
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
Common::g_scm_desc);
+#ifdef ARCHITECTURE_x86_64
+ LOG_INFO(Frontend, "Host CPU: {}", Common::GetCPUCaps().cpu_string);
+#endif
+ LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString());
UpdateWindowTitle();
show();
@@ -209,14 +224,13 @@ GMainWindow::GMainWindow()
std::make_unique<FileSys::ContentProviderUnion>());
Core::System::GetInstance().RegisterContentProvider(
FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
- Service::FileSystem::CreateFactories(*vfs);
+ Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
// Gen keys if necessary
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
game_list->LoadCompatibilityList();
- game_list->PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
// Show one-time "callout" messages to the user
ShowTelemetryCallout();
@@ -426,6 +440,10 @@ void GMainWindow::InitializeWidgets() {
game_list = new GameList(vfs, provider.get(), this);
ui.horizontalLayout->addWidget(game_list);
+ game_list_placeholder = new GameListPlaceholder(this);
+ ui.horizontalLayout->addWidget(game_list_placeholder);
+ game_list_placeholder->setVisible(false);
+
loading_screen = new LoadingScreen(this);
loading_screen->hide();
ui.horizontalLayout->addWidget(loading_screen);
@@ -659,6 +677,7 @@ void GMainWindow::RestoreUIState() {
void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
+ connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory);
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
&GMainWindow::OnTransferableShaderCacheOpenFile);
@@ -666,6 +685,11 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
+ connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
+ connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
+ &GMainWindow::OnGameListAddDirectory);
+ connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
+
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
@@ -683,8 +707,6 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
connect(ui.action_Install_File_NAND, &QAction::triggered, this,
&GMainWindow::OnMenuInstallToNAND);
- connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
- &GMainWindow::OnMenuSelectGameListRoot);
connect(ui.action_Select_NAND_Directory, &QAction::triggered, this,
[this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); });
connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
@@ -747,9 +769,24 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
+void GMainWindow::PreventOSSleep() {
+#ifdef _WIN32
+ SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
+#endif
+}
+
+void GMainWindow::AllowOSSleep() {
+#ifdef _WIN32
+ SetThreadExecutionState(ES_CONTINUOUS);
+#endif
+}
+
QStringList GMainWindow::GetUnsupportedGLExtensions() {
QStringList unsupported_ext;
+ if (!GLAD_GL_ARB_buffer_storage) {
+ unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
+ }
if (!GLAD_GL_ARB_direct_state_access) {
unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
}
@@ -814,11 +851,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetGPUDebugContext(debug_context);
system.SetAppletFrontendSet({
- std::make_unique<QtErrorDisplay>(*this),
- nullptr,
- std::make_unique<QtProfileSelector>(*this),
- std::make_unique<QtSoftwareKeyboard>(*this),
- std::make_unique<QtWebBrowser>(*this),
+ nullptr, // Parental Controls
+ std::make_unique<QtErrorDisplay>(*this), //
+ nullptr, // Photo Viewer
+ std::make_unique<QtProfileSelector>(*this), //
+ std::make_unique<QtSoftwareKeyboard>(*this), //
+ std::make_unique<QtWebBrowser>(*this), //
+ nullptr, // E-Commerce
});
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
@@ -932,14 +971,15 @@ void GMainWindow::BootGame(const QString& filename) {
// Update the GUI
if (ui.action_Single_Window_Mode->isChecked()) {
game_list->hide();
+ game_list_placeholder->hide();
}
status_bar_update_timer.start(2000);
+ const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+
std::string title_name;
const auto res = Core::System::GetInstance().GetGameName(title_name);
if (res != Loader::ResultStatus::Success) {
- const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
-
const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata();
if (nacp != nullptr)
title_name = nacp->GetApplicationName();
@@ -947,7 +987,7 @@ void GMainWindow::BootGame(const QString& filename) {
if (title_name.empty())
title_name = FileUtil::GetFilename(filename.toStdString());
}
-
+ LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name);
UpdateWindowTitle(QString::fromStdString(title_name));
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
@@ -961,6 +1001,8 @@ void GMainWindow::BootGame(const QString& filename) {
}
void GMainWindow::ShutdownGame() {
+ AllowOSSleep();
+
discord_rpc->Pause();
emu_thread->RequestStop();
@@ -987,7 +1029,10 @@ void GMainWindow::ShutdownGame() {
render_window->hide();
loading_screen->hide();
loading_screen->Clear();
- game_list->show();
+ if (game_list->isEmpty())
+ game_list_placeholder->show();
+ else
+ game_list->show();
game_list->setFilterFocus();
UpdateWindowTitle();
@@ -1278,6 +1323,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
}
+void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
+ QString path;
+ if (directory == QStringLiteral("SDMC")) {
+ path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
+ "Nintendo/Contents/registered");
+ } else if (directory == QStringLiteral("UserNAND")) {
+ path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "user/Contents/registered");
+ } else if (directory == QStringLiteral("SysNAND")) {
+ path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "system/Contents/registered");
+ } else {
+ path = directory;
+ }
+ if (!QFileInfo::exists(path)) {
+ QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!"));
+ return;
+ }
+ QDesktopServices::openUrl(QUrl::fromLocalFile(path));
+}
+
+void GMainWindow::OnGameListAddDirectory() {
+ const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
+ if (dir_path.isEmpty())
+ return;
+ UISettings::GameDir game_dir{dir_path, false, true};
+ if (!UISettings::values.game_dirs.contains(game_dir)) {
+ UISettings::values.game_dirs.append(game_dir);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+ } else {
+ LOG_WARNING(Frontend, "Selected directory is already in the game list");
+ }
+}
+
+void GMainWindow::OnGameListShowList(bool show) {
+ if (emulation_running && ui.action_Single_Window_Mode->isChecked())
+ return;
+ game_list->setVisible(show);
+ game_list_placeholder->setVisible(!show);
+};
+
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
u64 title_id{};
const auto v_file = Core::GetGameFileFromPath(vfs, file);
@@ -1296,8 +1382,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
- game_list->PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
}
config->Save();
@@ -1387,8 +1472,7 @@ void GMainWindow::OnMenuInstallToNAND() {
const auto success = [this]() {
QMessageBox::information(this, tr("Successfully Installed"),
tr("The file was successfully installed."));
- game_list->PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
DIR_SEP + "game_list");
};
@@ -1426,15 +1510,19 @@ void GMainWindow::OnMenuInstallToNAND() {
failed();
return;
}
- const auto res =
- Service::FileSystem::GetUserNANDContents()->InstallEntry(*nsp, false, qt_raw_copy);
+ const auto res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nsp, false, qt_raw_copy);
if (res == FileSys::InstallResult::Success) {
success();
} else {
if (res == FileSys::InstallResult::ErrorAlreadyExists) {
if (overwrite()) {
- const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
- *nsp, true, qt_raw_copy);
+ const auto res2 = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nsp, true, qt_raw_copy);
if (res2 == FileSys::InstallResult::Success) {
success();
} else {
@@ -1488,19 +1576,28 @@ void GMainWindow::OnMenuInstallToNAND() {
FileSys::InstallResult res;
if (index >= static_cast<size_t>(FileSys::TitleType::Application)) {
- res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
- *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
+ res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
+ qt_raw_copy);
} else {
- res = Service::FileSystem::GetSystemNANDContents()->InstallEntry(
- *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
+ res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetSystemNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
+ qt_raw_copy);
}
if (res == FileSys::InstallResult::Success) {
success();
} else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
if (overwrite()) {
- const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
- *nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
+ const auto res2 = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index),
+ true, qt_raw_copy);
if (res2 == FileSys::InstallResult::Success) {
success();
} else {
@@ -1513,14 +1610,6 @@ void GMainWindow::OnMenuInstallToNAND() {
}
}
-void GMainWindow::OnMenuSelectGameListRoot() {
- QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
- if (!dir_path.isEmpty()) {
- UISettings::values.game_directory_path = dir_path;
- game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
- }
-}
-
void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) {
const auto res = QMessageBox::information(
this, tr("Changing Emulated Directory"),
@@ -1538,9 +1627,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
: FileUtil::UserPath::NANDDir,
dir_path.toStdString());
- Service::FileSystem::CreateFactories(*vfs);
- game_list->PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
}
}
@@ -1562,6 +1650,8 @@ void GMainWindow::OnMenuRecentFile() {
}
void GMainWindow::OnStartGame() {
+ PreventOSSleep();
+
emu_thread->SetRunning(true);
qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
@@ -1587,15 +1677,27 @@ void GMainWindow::OnStartGame() {
}
void GMainWindow::OnPauseGame() {
+ Core::System& system{Core::System::GetInstance()};
+ if (system.GetExitLock() && !ConfirmForceLockedExit()) {
+ return;
+ }
+
emu_thread->SetRunning(false);
ui.action_Start->setEnabled(true);
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(true);
ui.action_Capture_Screenshot->setEnabled(false);
+
+ AllowOSSleep();
}
void GMainWindow::OnStopGame() {
+ Core::System& system{Core::System::GetInstance()};
+ if (system.GetExitLock() && !ConfirmForceLockedExit()) {
+ return;
+ }
+
ShutdownGame();
}
@@ -1700,11 +1802,11 @@ void GMainWindow::OnConfigure() {
if (UISettings::values.enable_discord_presence != old_discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence);
}
+ emit UpdateThemedIcons();
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
- game_list->PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
}
config->Save();
@@ -1838,13 +1940,14 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
"data, or other bugs.");
switch (result) {
case Core::System::ResultStatus::ErrorSystemFiles: {
- QString message = tr("yuzu was unable to locate a Switch system archive");
- if (!details.empty()) {
- message.append(tr(": %1. ").arg(QString::fromStdString(details)));
+ QString message;
+ if (details.empty()) {
+ message =
+ tr("yuzu was unable to locate a Switch system archive. %1").arg(common_message);
} else {
- message.append(tr(". "));
+ message = tr("yuzu was unable to locate a Switch system archive: %1. %2")
+ .arg(QString::fromStdString(details), common_message);
}
- message.append(common_message);
answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
@@ -1853,8 +1956,8 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
}
case Core::System::ResultStatus::ErrorSharedFont: {
- QString message = tr("yuzu was unable to locate the Switch shared fonts. ");
- message.append(common_message);
+ const QString message =
+ tr("yuzu was unable to locate the Switch shared fonts. %1").arg(common_message);
answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
status_message = tr("Shared Font Missing");
@@ -1919,7 +2022,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
const auto function = [this, &keys, &pdm] {
keys.PopulateFromPartitionData(pdm);
- Service::FileSystem::CreateFactories(*vfs);
+ Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
keys.DeriveETicket(pdm);
};
@@ -1944,7 +2047,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
errors +
tr("<br><br>You can get all of these and dump all of your games easily by "
"following <a href='https://yuzu-emu.org/help/quickstart/'>the "
- "quickstart guide</a>. Alternatively, you can use another method of dumping"
+ "quickstart guide</a>. Alternatively, you can use another method of dumping "
"to obtain all of your keys."));
}
@@ -1964,11 +2067,10 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
prog.close();
}
- Service::FileSystem::CreateFactories(*vfs);
+ Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
if (behavior == ReinitializeKeyBehavior::Warning) {
- game_list->PopulateAsync(UISettings::values.game_directory_path,
- UISettings::values.game_directory_deepscan);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
}
}
@@ -2093,13 +2195,41 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
- auto answer = QMessageBox::question(
+ const auto answer = QMessageBox::question(
this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return answer != QMessageBox::No;
}
+bool GMainWindow::ConfirmForceLockedExit() {
+ if (emu_thread == nullptr)
+ return true;
+
+ const auto answer =
+ QMessageBox::question(this, tr("yuzu"),
+ tr("The currently running application has requested yuzu to not "
+ "exit.\n\nWould you like to bypass this and exit anyway?"),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+ return answer != QMessageBox::No;
+}
+
+void GMainWindow::RequestGameExit() {
+ auto& sm{Core::System::GetInstance().ServiceManager()};
+ auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
+ auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
+ bool has_signalled = false;
+
+ if (applet_oe != nullptr) {
+ applet_oe->GetMessageQueue()->RequestExit();
+ has_signalled = true;
+ }
+
+ if (applet_ae != nullptr && !has_signalled) {
+ applet_ae->GetMessageQueue()->RequestExit();
+ }
+}
+
void GMainWindow::filterBarSetChecked(bool state) {
ui.action_Show_Filter_Bar->setChecked(state);
emit(OnToggleFilterBar());
@@ -2133,7 +2263,6 @@ void GMainWindow::UpdateUITheme() {
}
QIcon::setThemeSearchPaths(theme_paths);
- emit UpdateThemedIcons();
}
void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
@@ -2162,6 +2291,14 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
+#ifdef __APPLE__
+ // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
+ // But since we require the working directory to be the executable path for the location of the
+ // user folder in the Qt Frontend, we need to cd into that working directory
+ const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
+ chdir(bin_path.c_str());
+#endif
+
// Enables the core to make the qt created contexts current on std::threads
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 1137bbc7a..e942d1248 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -30,6 +30,7 @@ class ProfilerWidget;
class QLabel;
class WaitTreeWidget;
enum class GameListOpenTarget;
+class GameListPlaceholder;
namespace Core::Frontend {
struct SoftwareKeyboardParameters;
@@ -130,6 +131,9 @@ private:
void ConnectWidgetEvents();
void ConnectMenuEvents();
+ void PreventOSSleep();
+ void AllowOSSleep();
+
QStringList GetUnsupportedGLExtensions();
bool LoadROM(const QString& filename);
void BootGame(const QString& filename);
@@ -168,6 +172,8 @@ private:
*/
bool ConfirmClose();
bool ConfirmChangeGame();
+ bool ConfirmForceLockedExit();
+ void RequestGameExit();
void closeEvent(QCloseEvent* event) override;
private slots:
@@ -183,12 +189,13 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
+ void OnGameListOpenDirectory(const QString& directory);
+ void OnGameListAddDirectory();
+ void OnGameListShowList(bool show);
void OnGameListOpenPerGameProperties(const std::string& file);
void OnMenuLoadFile();
void OnMenuLoadFolder();
void OnMenuInstallToNAND();
- /// Called whenever a user selects the "File->Select Game List Root" menu item
- void OnMenuSelectGameListRoot();
/// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card
void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
void OnMenuRecentFile();
@@ -220,6 +227,8 @@ private:
GameList* game_list;
LoadingScreen* loading_screen;
+ GameListPlaceholder* game_list_placeholder;
+
// Status bar elements
QLabel* message_label = nullptr;
QLabel* emu_speed_label = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index ffcabb495..a1ce3c0c3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -62,7 +62,6 @@
<addaction name="action_Load_File"/>
<addaction name="action_Load_Folder"/>
<addaction name="separator"/>
- <addaction name="action_Select_Game_List_Root"/>
<addaction name="menu_recent_files"/>
<addaction name="separator"/>
<addaction name="action_Select_NAND_Directory"/>
diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/uisettings.cpp
index 4bdc302e0..7f7d247a3 100644
--- a/src/yuzu/ui_settings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -2,7 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "ui_settings.h"
+#include "yuzu/uisettings.h"
namespace UISettings {
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/uisettings.h
index a62cd6911..c57290006 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/uisettings.h
@@ -8,8 +8,10 @@
#include <atomic>
#include <vector>
#include <QByteArray>
+#include <QMetaType>
#include <QString>
#include <QStringList>
+#include <QVector>
#include "common/common_types.h"
namespace UISettings {
@@ -25,6 +27,18 @@ struct Shortcut {
using Themes = std::array<std::pair<const char*, const char*>, 2>;
extern const Themes themes;
+struct GameDir {
+ QString path;
+ bool deep_scan;
+ bool expanded;
+ bool operator==(const GameDir& rhs) const {
+ return path == rhs.path;
+ };
+ bool operator!=(const GameDir& rhs) const {
+ return !operator==(rhs);
+ };
+};
+
struct Values {
QByteArray geometry;
QByteArray state;
@@ -55,8 +69,9 @@ struct Values {
QString roms_path;
QString symbols_path;
QString screenshot_path;
- QString game_directory_path;
- bool game_directory_deepscan;
+ QString game_dir_deprecated;
+ bool game_dir_deprecated_deepscan;
+ QVector<UISettings::GameDir> game_dirs;
QStringList recent_files;
QString theme;
@@ -84,3 +99,5 @@ struct Values {
extern Values values;
} // namespace UISettings
+
+Q_DECLARE_METATYPE(UISettings::GameDir*);