summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt24
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.cpp3
-rw-r--r--src/yuzu/applets/qt_controller.cpp14
-rw-r--r--src/yuzu/bootmanager.cpp16
-rw-r--r--src/yuzu/configuration/config.cpp726
-rw-r--r--src/yuzu/configuration/config.h67
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp103
-rw-r--r--src/yuzu/configuration/configuration_shared.h78
-rw-r--r--src/yuzu/configuration/configure.ui31
-rw-r--r--src/yuzu/configuration/configure_audio.cpp208
-rw-r--r--src/yuzu/configuration/configure_audio.h32
-rw-r--r--src/yuzu/configuration/configure_audio.ui162
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp149
-rw-r--r--src/yuzu/configuration/configure_cpu.h37
-rw-r--r--src/yuzu/configuration/configure_cpu.ui179
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui865
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp27
-rw-r--r--src/yuzu/configuration/configure_dialog.h7
-rw-r--r--src/yuzu/configuration/configure_general.cpp116
-rw-r--r--src/yuzu/configuration/configure_general.h29
-rw-r--r--src/yuzu/configuration/configure_general.ui87
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp663
-rw-r--r--src/yuzu/configuration/configure_graphics.h63
-rw-r--r--src/yuzu/configuration/configure_graphics.ui690
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp187
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h39
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui231
-rw-r--r--src/yuzu/configuration/configure_input.cpp14
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp38
-rw-r--r--src/yuzu/configuration/configure_per_game.h7
-rw-r--r--src/yuzu/configuration/configure_per_game.ui47
-rw-r--r--src/yuzu/configuration/configure_system.cpp217
-rw-r--r--src/yuzu/configuration/configure_system.h36
-rw-r--r--src/yuzu/configuration/configure_system.ui537
-rw-r--r--src/yuzu/configuration/configure_ui.cpp100
-rw-r--r--src/yuzu/configuration/configure_ui.h8
-rw-r--r--src/yuzu/configuration/configure_ui.ui44
-rw-r--r--src/yuzu/configuration/shared_translation.cpp392
-rw-r--r--src/yuzu/configuration/shared_translation.h25
-rw-r--r--src/yuzu/configuration/shared_widget.cpp802
-rw-r--r--src/yuzu/configuration/shared_widget.h178
-rw-r--r--src/yuzu/discord_impl.cpp82
-rw-r--r--src/yuzu/discord_impl.h7
-rw-r--r--src/yuzu/game_list.cpp35
-rw-r--r--src/yuzu/game_list.h11
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/game_list_worker.cpp38
-rw-r--r--src/yuzu/game_list_worker.h6
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp719
-rw-r--r--src/yuzu/main.h34
-rw-r--r--src/yuzu/main.ui49
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp9
-rw-r--r--src/yuzu/multiplayer/host_room.cpp16
-rw-r--r--src/yuzu/multiplayer/lobby.cpp7
-rw-r--r--src/yuzu/play_time_manager.cpp179
-rw-r--r--src/yuzu/play_time_manager.h44
-rw-r--r--src/yuzu/uisettings.cpp28
-rw-r--r--src/yuzu/uisettings.h136
-rw-r--r--src/yuzu/util/util.cpp77
-rw-r--r--src/yuzu/util/util.h14
-rw-r--r--src/yuzu/vk_device_info.cpp4
63 files changed, 4583 insertions, 4223 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index fe98e3605..9ebece907 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -143,6 +143,10 @@ add_executable(yuzu
configuration/configure_web.ui
configuration/input_profiles.cpp
configuration/input_profiles.h
+ configuration/shared_translation.cpp
+ configuration/shared_translation.h
+ configuration/shared_widget.cpp
+ configuration/shared_widget.h
debugger/console.cpp
debugger/console.h
debugger/controller.cpp
@@ -191,6 +195,8 @@ add_executable(yuzu
multiplayer/state.cpp
multiplayer/state.h
multiplayer/validation.h
+ play_time_manager.cpp
+ play_time_manager.h
precompiled_headers.h
qt_common.cpp
qt_common.h
@@ -231,6 +237,12 @@ if (WIN32 AND YUZU_CRASH_DUMPS)
target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
endif()
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ target_compile_definitions(yuzu PRIVATE
+ $<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>
+ )
+endif()
+
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
@@ -303,6 +315,18 @@ if (APPLE)
target_sources(yuzu PRIVATE ${MACOSX_ICON})
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
+
+ if (NOT USE_SYSTEM_MOLTENVK)
+ set(MOLTENVK_PLATFORM "macOS")
+ set(MOLTENVK_VERSION "v1.2.5")
+ download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION})
+ endif()
+ find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
+ message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
+ set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks
+ XCODE_FILE_ATTRIBUTES "CodeSignOnCopy")
+ target_sources(yuzu PRIVATE ${MOLTENVK_LIBRARY})
+
elseif(WIN32)
# compile as a win32 gui application instead of a console application
if (QT_VERSION VERSION_GREATER_EQUAL 6)
diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp
index 4988fcc83..b457a736a 100644
--- a/src/yuzu/applets/qt_amiibo_settings.cpp
+++ b/src/yuzu/applets/qt_amiibo_settings.cpp
@@ -160,7 +160,8 @@ void QtAmiiboSettingsDialog::LoadAmiiboData() {
}
const auto amiibo_name = std::string(register_info.amiibo_name.data());
- const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data());
+ const auto owner_name =
+ Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data());
const auto creation_date =
QDate(register_info.creation_date.year, register_info.creation_date.month,
register_info.creation_date.day);
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 3b8317598..ca0e14fad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -5,6 +5,8 @@
#include <thread>
#include "common/assert.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
@@ -240,9 +242,11 @@ int QtControllerSelectorDialog::exec() {
}
void QtControllerSelectorDialog::ApplyConfiguration() {
- const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
- Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system);
+ const bool pre_docked_mode = Settings::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -655,8 +659,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+ ui->radioDocked->setChecked(Settings::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index bdd1497b5..2afa72140 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -11,6 +11,8 @@
#include <glad/glad.h>
#include <QtCore/qglobal.h>
+#include "common/settings_enums.h"
+#include "uisettings.h"
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
#include <QCamera>
#include <QCameraImageCapture>
@@ -916,7 +918,6 @@ void GRenderWindow::ReleaseRenderTarget() {
void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
auto& renderer = system.Renderer();
- const f32 res_scale = Settings::values.resolution_info.up_factor;
if (renderer.IsScreenshotPending()) {
LOG_WARNING(Render,
@@ -924,7 +925,18 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
return;
}
- const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
+ const Layout::FramebufferLayout layout{[]() {
+ u32 height = UISettings::values.screenshot_height.GetValue();
+ if (height == 0) {
+ height = Settings::IsDockedMode() ? Layout::ScreenDocked::Height
+ : Layout::ScreenUndocked::Height;
+ height *= Settings::values.resolution_info.up_factor;
+ }
+ const u32 width =
+ UISettings::CalculateWidth(height, Settings::values.aspect_ratio.GetValue());
+ return Layout::DefaultFrameLayout(width, height);
+ }()};
+
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(
screenshot_image.bits(),
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 195d3556c..1de093447 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -1,12 +1,15 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
#include <array>
#include <QKeySequence>
#include <QSettings>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
+#include "common/settings_common.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
@@ -16,9 +19,8 @@
namespace FS = Common::FS;
-Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
- global = config_type == ConfigType::GlobalConfig;
-
+Config::Config(const std::string& config_name, ConfigType config_type)
+ : type(config_type), global{config_type == ConfigType::GlobalConfig} {
Initialize(config_name);
}
@@ -84,15 +86,15 @@ const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_ma
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
};
-const std::map<bool, QString> Config::use_docked_mode_texts_map = {
- {true, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
- {false, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
+const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = {
+ {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
+ {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
};
-const std::map<Settings::GPUAccuracy, QString> Config::gpu_accuracy_texts_map = {
- {Settings::GPUAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
- {Settings::GPUAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
- {Settings::GPUAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
+const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = {
+ {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
+ {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
+ {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
};
const std::map<Settings::RendererBackend, QString> Config::renderer_backend_texts_map = {
@@ -102,9 +104,9 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
};
const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
- {Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
- {Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
- {Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
+ {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
+ {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
+ {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
};
// This shouldn't have anything except static initializers (no functions). So
@@ -171,66 +173,6 @@ bool Config::IsCustomConfig() {
return type == ConfigType::PerGameConfig;
}
-/* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their
- * usages later in this file. This allows explicit definition of some types that don't work
- * nicely with the general version.
- */
-
-// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor
-// can it implicitly convert a QVariant back to a {std::,Q}string
-template <>
-void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const auto default_value = QString::fromStdString(setting.GetDefault());
- if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
- setting.SetValue(default_value.toStdString());
- } else {
- setting.SetValue(qt_config->value(name, default_value).toString().toStdString());
- }
-}
-
-template <typename Type, bool ranged>
-void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const Type default_value = setting.GetDefault();
- if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
- setting.SetValue(default_value);
- } else {
- setting.SetValue(
- static_cast<QVariant>(qt_config->value(name, default_value)).value<Type>());
- }
-}
-
-// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant
-template <>
-void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const std::string& value = setting.GetValue();
- qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
- qt_config->setValue(name, QString::fromStdString(value));
-}
-
-template <typename Type, bool ranged>
-void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const Type value = setting.GetValue();
- qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
- qt_config->setValue(name, value);
-}
-
-template <typename Type, bool ranged>
-void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
- const QString name = QString::fromStdString(setting.GetLabel());
- const Type& value = setting.GetValue(global);
- if (!global) {
- qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
- }
- if (global || !setting.UsingGlobal()) {
- qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
- qt_config->setValue(name, value);
- }
-}
-
void Config::ReadPlayerValue(std::size_t player_index) {
const QString player_prefix = [this, player_index] {
if (type == ConfigType::InputProfile) {
@@ -351,15 +293,9 @@ void Config::ReadPlayerValue(std::size_t player_index) {
player_motions = default_param;
}
}
-
- if (player_index == 0) {
- ReadMousePanningValues();
- }
}
void Config::ReadDebugValues() {
- ReadBasicSetting(Settings::values.debug_pad_enabled);
-
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
@@ -393,14 +329,6 @@ void Config::ReadDebugValues() {
}
}
-void Config::ReadKeyboardValues() {
- ReadBasicSetting(Settings::values.keyboard_enabled);
-}
-
-void Config::ReadMouseValues() {
- ReadBasicSetting(Settings::values.mouse_enabled);
-}
-
void Config::ReadTouchscreenValues() {
Settings::values.touchscreen.enabled =
ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
@@ -414,9 +342,6 @@ void Config::ReadTouchscreenValues() {
}
void Config::ReadHidbusValues() {
- Settings::values.enable_ring_controller =
- ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool();
-
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
auto& ringcon_analogs = Settings::values.ringcon_analogs;
@@ -430,20 +355,10 @@ void Config::ReadHidbusValues() {
}
}
-void Config::ReadIrCameraValues() {
- ReadBasicSetting(Settings::values.enable_ir_sensor);
- ReadBasicSetting(Settings::values.ir_sensor_device);
-}
-
void Config::ReadAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
- if (global) {
- ReadBasicSetting(Settings::values.sink_id);
- ReadBasicSetting(Settings::values.audio_output_device_id);
- ReadBasicSetting(Settings::values.audio_input_device_id);
- }
- ReadGlobalSetting(Settings::values.volume);
+ ReadCategory(Settings::Category::Audio);
qt_config->endGroup();
}
@@ -451,63 +366,32 @@ void Config::ReadAudioValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
+ ReadCategory(Settings::Category::Controls);
+
Settings::values.players.SetGlobal(!IsCustomConfig());
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
ReadPlayerValue(p);
}
- ReadGlobalSetting(Settings::values.use_docked_mode);
// Disable docked mode if handheld is selected
const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
if (controller_type == Settings::ControllerType::Handheld) {
Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
- Settings::values.use_docked_mode.SetValue(false);
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
}
- ReadGlobalSetting(Settings::values.vibration_enabled);
- ReadGlobalSetting(Settings::values.enable_accurate_vibrations);
- ReadGlobalSetting(Settings::values.motion_enabled);
if (IsCustomConfig()) {
qt_config->endGroup();
return;
}
ReadDebugValues();
- ReadKeyboardValues();
- ReadMouseValues();
ReadTouchscreenValues();
- ReadMousePanningValues();
ReadMotionTouchValues();
ReadHidbusValues();
- ReadIrCameraValues();
-
-#ifdef _WIN32
- ReadBasicSetting(Settings::values.enable_raw_input);
-#else
- Settings::values.enable_raw_input = false;
-#endif
- ReadBasicSetting(Settings::values.emulate_analog_keyboard);
- ReadBasicSetting(Settings::values.enable_joycon_driver);
- ReadBasicSetting(Settings::values.enable_procon_driver);
- ReadBasicSetting(Settings::values.random_amiibo_id);
-
- ReadBasicSetting(Settings::values.tas_enable);
- ReadBasicSetting(Settings::values.tas_loop);
- ReadBasicSetting(Settings::values.pause_tas_on_load);
-
- ReadBasicSetting(Settings::values.controller_navigation);
qt_config->endGroup();
}
-void Config::ReadMousePanningValues() {
- ReadBasicSetting(Settings::values.mouse_panning);
- ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity);
- ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity);
- ReadBasicSetting(Settings::values.mouse_panning_deadzone_counterweight);
- ReadBasicSetting(Settings::values.mouse_panning_decay_strength);
- ReadBasicSetting(Settings::values.mouse_panning_min_decay);
-}
-
void Config::ReadMotionTouchValues() {
int num_touch_from_button_maps =
qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
@@ -541,19 +425,14 @@ void Config::ReadMotionTouchValues() {
}
qt_config->endArray();
- ReadBasicSetting(Settings::values.touch_device);
- ReadBasicSetting(Settings::values.touch_from_button_map_index);
Settings::values.touch_from_button_map_index = std::clamp(
Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
- ReadBasicSetting(Settings::values.udp_input_servers);
- ReadBasicSetting(Settings::values.enable_udp_controller);
}
void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- ReadGlobalSetting(Settings::values.use_multi_core);
- ReadGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);
+ ReadCategory(Settings::Category::Core);
qt_config->endGroup();
}
@@ -561,7 +440,6 @@ void Config::ReadCoreValues() {
void Config::ReadDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
- ReadBasicSetting(Settings::values.use_virtual_sd);
FS::SetYuzuPath(
FS::YuzuPath::NANDDir,
qt_config
@@ -597,9 +475,7 @@ void Config::ReadDataStorageValues() {
.toString()
.toStdString());
- ReadBasicSetting(Settings::values.gamecard_inserted);
- ReadBasicSetting(Settings::values.gamecard_current_game);
- ReadBasicSetting(Settings::values.gamecard_path);
+ ReadCategory(Settings::Category::DataStorage);
qt_config->endGroup();
}
@@ -611,29 +487,17 @@ void Config::ReadDebuggingValues() {
Settings::values.record_frame_times =
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
- ReadBasicSetting(Settings::values.use_gdbstub);
- ReadBasicSetting(Settings::values.gdbstub_port);
- ReadBasicSetting(Settings::values.program_args);
- ReadBasicSetting(Settings::values.dump_exefs);
- ReadBasicSetting(Settings::values.dump_nso);
- ReadBasicSetting(Settings::values.enable_fs_access_log);
- ReadBasicSetting(Settings::values.reporting_services);
- ReadBasicSetting(Settings::values.quest_flag);
- ReadBasicSetting(Settings::values.disable_macro_jit);
- ReadBasicSetting(Settings::values.disable_macro_hle);
- ReadBasicSetting(Settings::values.extended_logging);
- ReadBasicSetting(Settings::values.use_debug_asserts);
- ReadBasicSetting(Settings::values.use_auto_stub);
- ReadBasicSetting(Settings::values.enable_all_controllers);
- ReadBasicSetting(Settings::values.create_crash_dumps);
- ReadBasicSetting(Settings::values.perform_vulkan_check);
+ ReadCategory(Settings::Category::Debugging);
+ ReadCategory(Settings::Category::DebuggingGraphics);
qt_config->endGroup();
}
void Config::ReadServiceValues() {
qt_config->beginGroup(QStringLiteral("Services"));
- ReadBasicSetting(Settings::values.network_interface);
+
+ ReadCategory(Settings::Category::Services);
+
qt_config->endGroup();
}
@@ -659,8 +523,7 @@ void Config::ReadDisabledAddOnValues() {
void Config::ReadMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
- ReadBasicSetting(Settings::values.log_filter);
- ReadBasicSetting(Settings::values.use_dev_keys);
+ ReadCategory(Settings::Category::Miscellaneous);
qt_config->endGroup();
}
@@ -710,36 +573,9 @@ void Config::ReadPathValues() {
void Config::ReadCpuValues() {
qt_config->beginGroup(QStringLiteral("Cpu"));
- ReadBasicSetting(Settings::values.cpu_accuracy_first_time);
- if (Settings::values.cpu_accuracy_first_time) {
- Settings::values.cpu_accuracy.SetValue(Settings::values.cpu_accuracy.GetDefault());
- Settings::values.cpu_accuracy_first_time.SetValue(false);
- } else {
- ReadGlobalSetting(Settings::values.cpu_accuracy);
- }
-
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_unfuse_fma);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_reduce_fp_error);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_inaccurate_nan);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_fastmem_check);
- ReadGlobalSetting(Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
- if (global) {
- ReadBasicSetting(Settings::values.cpu_debug_mode);
- ReadBasicSetting(Settings::values.cpuopt_page_tables);
- ReadBasicSetting(Settings::values.cpuopt_block_linking);
- ReadBasicSetting(Settings::values.cpuopt_return_stack_buffer);
- ReadBasicSetting(Settings::values.cpuopt_fast_dispatcher);
- ReadBasicSetting(Settings::values.cpuopt_context_elimination);
- ReadBasicSetting(Settings::values.cpuopt_const_prop);
- ReadBasicSetting(Settings::values.cpuopt_misc_ir);
- ReadBasicSetting(Settings::values.cpuopt_reduce_misalign_checks);
- ReadBasicSetting(Settings::values.cpuopt_fastmem);
- ReadBasicSetting(Settings::values.cpuopt_fastmem_exclusives);
- ReadBasicSetting(Settings::values.cpuopt_recompile_exclusives);
- ReadBasicSetting(Settings::values.cpuopt_ignore_memory_aborts);
- }
+ ReadCategory(Settings::Category::Cpu);
+ ReadCategory(Settings::Category::CpuDebug);
+ ReadCategory(Settings::Category::CpuUnsafe);
qt_config->endGroup();
}
@@ -747,47 +583,9 @@ void Config::ReadCpuValues() {
void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
- ReadGlobalSetting(Settings::values.renderer_backend);
- ReadGlobalSetting(Settings::values.async_presentation);
- ReadGlobalSetting(Settings::values.renderer_force_max_clock);
- ReadGlobalSetting(Settings::values.vulkan_device);
- ReadGlobalSetting(Settings::values.fullscreen_mode);
- ReadGlobalSetting(Settings::values.aspect_ratio);
- ReadGlobalSetting(Settings::values.resolution_setup);
- ReadGlobalSetting(Settings::values.scaling_filter);
- ReadGlobalSetting(Settings::values.fsr_sharpening_slider);
- ReadGlobalSetting(Settings::values.anti_aliasing);
- ReadGlobalSetting(Settings::values.max_anisotropy);
- ReadGlobalSetting(Settings::values.speed_limit);
- ReadGlobalSetting(Settings::values.use_disk_shader_cache);
- ReadGlobalSetting(Settings::values.gpu_accuracy);
- ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
- ReadGlobalSetting(Settings::values.nvdec_emulation);
- ReadGlobalSetting(Settings::values.accelerate_astc);
- ReadGlobalSetting(Settings::values.async_astc);
- ReadGlobalSetting(Settings::values.astc_recompression);
- ReadGlobalSetting(Settings::values.use_reactive_flushing);
- ReadGlobalSetting(Settings::values.shader_backend);
- ReadGlobalSetting(Settings::values.use_asynchronous_shaders);
- ReadGlobalSetting(Settings::values.use_fast_gpu_time);
- ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
- ReadGlobalSetting(Settings::values.enable_compute_pipelines);
- ReadGlobalSetting(Settings::values.use_video_framerate);
- ReadGlobalSetting(Settings::values.barrier_feedback_loops);
- ReadGlobalSetting(Settings::values.bg_red);
- ReadGlobalSetting(Settings::values.bg_green);
- ReadGlobalSetting(Settings::values.bg_blue);
-
- if (global) {
- Settings::values.vsync_mode.SetValue(static_cast<Settings::VSyncMode>(
- ReadSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()),
- static_cast<u32>(Settings::values.vsync_mode.GetDefault()))
- .value<u32>()));
- ReadBasicSetting(Settings::values.renderer_debug);
- ReadBasicSetting(Settings::values.renderer_shader_feedback);
- ReadBasicSetting(Settings::values.enable_nsight_aftermath);
- ReadBasicSetting(Settings::values.disable_shader_loop_safety_checks);
- }
+ ReadCategory(Settings::Category::Renderer);
+ ReadCategory(Settings::Category::RendererAdvanced);
+ ReadCategory(Settings::Category::RendererDebug);
qt_config->endGroup();
}
@@ -795,8 +593,7 @@ void Config::ReadRendererValues() {
void Config::ReadScreenshotValues() {
qt_config->beginGroup(QStringLiteral("Screenshots"));
- UISettings::values.enable_screenshot_save_as =
- ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
+ ReadCategory(Settings::Category::Screenshots);
FS::SetYuzuPath(
FS::YuzuPath::ScreenshotsDir,
qt_config
@@ -834,41 +631,8 @@ void Config::ReadShortcutValues() {
void Config::ReadSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
- ReadGlobalSetting(Settings::values.language_index);
-
- ReadGlobalSetting(Settings::values.region_index);
-
- ReadGlobalSetting(Settings::values.time_zone_index);
-
- bool rng_seed_enabled;
- ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false);
- bool rng_seed_global =
- global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool();
- Settings::values.rng_seed.SetGlobal(rng_seed_global);
- if (global || !rng_seed_global) {
- if (rng_seed_enabled) {
- Settings::values.rng_seed.SetValue(ReadSetting(QStringLiteral("rng_seed"), 0).toUInt());
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
- }
-
- if (global) {
- ReadBasicSetting(Settings::values.current_user);
- Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
- Service::Account::MAX_USERS - 1);
-
- const auto custom_rtc_enabled =
- ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();
- if (custom_rtc_enabled) {
- Settings::values.custom_rtc = ReadSetting(QStringLiteral("custom_rtc"), 0).toLongLong();
- } else {
- Settings::values.custom_rtc = std::nullopt;
- }
- ReadBasicSetting(Settings::values.device_name);
- }
-
- ReadGlobalSetting(Settings::values.sound_index);
+ ReadCategory(Settings::Category::System);
+ ReadCategory(Settings::Category::SystemAudio);
qt_config->endGroup();
}
@@ -881,8 +645,6 @@ void Config::ReadUIValues() {
QStringLiteral("theme"),
QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second))
.toString();
- ReadBasicSetting(UISettings::values.enable_discord_presence);
- ReadBasicSetting(UISettings::values.select_user_on_boot);
ReadUIGamelistValues();
ReadUILayoutValues();
@@ -891,20 +653,8 @@ void Config::ReadUIValues() {
ReadShortcutValues();
ReadMultiplayerValues();
- ReadBasicSetting(UISettings::values.single_window_mode);
- ReadBasicSetting(UISettings::values.fullscreen);
- ReadBasicSetting(UISettings::values.display_titlebar);
- ReadBasicSetting(UISettings::values.show_filter_bar);
- ReadBasicSetting(UISettings::values.show_status_bar);
- ReadBasicSetting(UISettings::values.confirm_before_closing);
- ReadBasicSetting(UISettings::values.first_start);
- ReadBasicSetting(UISettings::values.callout_flags);
- ReadBasicSetting(UISettings::values.show_console);
- ReadBasicSetting(UISettings::values.pause_when_in_background);
- ReadBasicSetting(UISettings::values.mute_when_in_background);
- ReadBasicSetting(UISettings::values.hide_mouse);
- ReadBasicSetting(UISettings::values.controller_applet_disabled);
- ReadBasicSetting(UISettings::values.disable_web_applet);
+ ReadCategory(Settings::Category::Ui);
+ ReadCategory(Settings::Category::UiGeneral);
qt_config->endGroup();
}
@@ -912,16 +662,8 @@ void Config::ReadUIValues() {
void Config::ReadUIGamelistValues() {
qt_config->beginGroup(QStringLiteral("UIGameList"));
- ReadBasicSetting(UISettings::values.show_add_ons);
- ReadBasicSetting(UISettings::values.show_compat);
- ReadBasicSetting(UISettings::values.show_size);
- ReadBasicSetting(UISettings::values.show_types);
- ReadBasicSetting(UISettings::values.game_icon_size);
- ReadBasicSetting(UISettings::values.folder_icon_size);
- ReadBasicSetting(UISettings::values.row_1_text_id);
- ReadBasicSetting(UISettings::values.row_2_text_id);
- ReadBasicSetting(UISettings::values.cache_game_list);
- ReadBasicSetting(UISettings::values.favorites_expanded);
+ ReadCategory(Settings::Category::UiGameList);
+
const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
for (int i = 0; i < favorites_size; i++) {
qt_config->setArrayIndex(i);
@@ -944,7 +686,8 @@ void Config::ReadUILayoutValues() {
ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
UISettings::values.microprofile_geometry =
ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
- ReadBasicSetting(UISettings::values.microprofile_visible);
+
+ ReadCategory(Settings::Category::UiLayout);
qt_config->endGroup();
}
@@ -952,10 +695,7 @@ void Config::ReadUILayoutValues() {
void Config::ReadWebServiceValues() {
qt_config->beginGroup(QStringLiteral("WebService"));
- ReadBasicSetting(Settings::values.enable_telemetry);
- ReadBasicSetting(Settings::values.web_api_url);
- ReadBasicSetting(Settings::values.yuzu_username);
- ReadBasicSetting(Settings::values.yuzu_token);
+ ReadCategory(Settings::Category::WebService);
qt_config->endGroup();
}
@@ -963,17 +703,7 @@ void Config::ReadWebServiceValues() {
void Config::ReadMultiplayerValues() {
qt_config->beginGroup(QStringLiteral("Multiplayer"));
- ReadBasicSetting(UISettings::values.multiplayer_nickname);
- ReadBasicSetting(UISettings::values.multiplayer_ip);
- ReadBasicSetting(UISettings::values.multiplayer_port);
- ReadBasicSetting(UISettings::values.multiplayer_room_nickname);
- ReadBasicSetting(UISettings::values.multiplayer_room_name);
- ReadBasicSetting(UISettings::values.multiplayer_room_port);
- ReadBasicSetting(UISettings::values.multiplayer_host_type);
- ReadBasicSetting(UISettings::values.multiplayer_port);
- ReadBasicSetting(UISettings::values.multiplayer_max_player);
- ReadBasicSetting(UISettings::values.multiplayer_game_id);
- ReadBasicSetting(UISettings::values.multiplayer_room_description);
+ ReadCategory(Settings::Category::Multiplayer);
// Read ban list back
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
@@ -996,11 +726,20 @@ void Config::ReadMultiplayerValues() {
qt_config->endGroup();
}
+void Config::ReadNetworkValues() {
+ qt_config->beginGroup(QString::fromStdString("Services"));
+
+ ReadCategory(Settings::Category::Network);
+
+ qt_config->endGroup();
+}
+
void Config::ReadValues() {
if (global) {
ReadDataStorageValues();
ReadDebuggingValues();
ReadDisabledAddOnValues();
+ ReadNetworkValues();
ReadServiceValues();
ReadUIValues();
ReadWebServiceValues();
@@ -1077,14 +816,9 @@ void Config::SavePlayerValue(std::size_t player_index) {
QString::fromStdString(player.motions[i]),
QString::fromStdString(default_param));
}
-
- if (player_index == 0) {
- SaveMousePanningValues();
- }
}
void Config::SaveDebugValues() {
- WriteBasicSetting(Settings::values.debug_pad_enabled);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(QStringLiteral("debug_pad_") +
@@ -1103,10 +837,6 @@ void Config::SaveDebugValues() {
}
}
-void Config::SaveMouseValues() {
- WriteBasicSetting(Settings::values.mouse_enabled);
-}
-
void Config::SaveTouchscreenValues() {
const auto& touchscreen = Settings::values.touchscreen;
@@ -1117,21 +847,7 @@ void Config::SaveTouchscreenValues() {
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
}
-void Config::SaveMousePanningValues() {
- // Don't overwrite values.mouse_panning
- WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity);
- WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity);
- WriteBasicSetting(Settings::values.mouse_panning_deadzone_counterweight);
- WriteBasicSetting(Settings::values.mouse_panning_decay_strength);
- WriteBasicSetting(Settings::values.mouse_panning_min_decay);
-}
-
void Config::SaveMotionTouchValues() {
- WriteBasicSetting(Settings::values.touch_device);
- WriteBasicSetting(Settings::values.touch_from_button_map_index);
- WriteBasicSetting(Settings::values.udp_input_servers);
- WriteBasicSetting(Settings::values.enable_udp_controller);
-
qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
qt_config->setArrayIndex(static_cast<int>(p));
@@ -1152,8 +868,6 @@ void Config::SaveMotionTouchValues() {
}
void Config::SaveHidbusValues() {
- WriteBasicSetting(Settings::values.enable_ring_controller);
-
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
WriteSetting(QStringLiteral("ring_controller"),
@@ -1161,11 +875,6 @@ void Config::SaveHidbusValues() {
QString::fromStdString(default_param));
}
-void Config::SaveIrCameraValues() {
- WriteBasicSetting(Settings::values.enable_ir_sensor);
- WriteBasicSetting(Settings::values.ir_sensor_device);
-}
-
void Config::SaveValues() {
if (global) {
SaveDataStorageValues();
@@ -1182,18 +891,14 @@ void Config::SaveValues() {
SaveRendererValues();
SaveAudioValues();
SaveSystemValues();
+
qt_config->sync();
}
void Config::SaveAudioValues() {
qt_config->beginGroup(QStringLiteral("Audio"));
- if (global) {
- WriteBasicSetting(Settings::values.sink_id);
- WriteBasicSetting(Settings::values.audio_output_device_id);
- WriteBasicSetting(Settings::values.audio_input_device_id);
- }
- WriteGlobalSetting(Settings::values.volume);
+ WriteCategory(Settings::Category::Audio);
qt_config->endGroup();
}
@@ -1201,6 +906,8 @@ void Config::SaveAudioValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
+ WriteCategory(Settings::Category::Controls);
+
Settings::values.players.SetGlobal(!IsCustomConfig());
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
SavePlayerValue(p);
@@ -1210,28 +917,9 @@ void Config::SaveControlValues() {
return;
}
SaveDebugValues();
- SaveMouseValues();
SaveTouchscreenValues();
- SaveMousePanningValues();
SaveMotionTouchValues();
SaveHidbusValues();
- SaveIrCameraValues();
-
- WriteGlobalSetting(Settings::values.use_docked_mode);
- WriteGlobalSetting(Settings::values.vibration_enabled);
- WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
- WriteGlobalSetting(Settings::values.motion_enabled);
- WriteBasicSetting(Settings::values.enable_raw_input);
- WriteBasicSetting(Settings::values.enable_joycon_driver);
- WriteBasicSetting(Settings::values.enable_procon_driver);
- WriteBasicSetting(Settings::values.random_amiibo_id);
- WriteBasicSetting(Settings::values.keyboard_enabled);
- WriteBasicSetting(Settings::values.emulate_analog_keyboard);
- WriteBasicSetting(Settings::values.controller_navigation);
-
- WriteBasicSetting(Settings::values.tas_enable);
- WriteBasicSetting(Settings::values.tas_loop);
- WriteBasicSetting(Settings::values.pause_tas_on_load);
qt_config->endGroup();
}
@@ -1239,8 +927,7 @@ void Config::SaveControlValues() {
void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- WriteGlobalSetting(Settings::values.use_multi_core);
- WriteGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);
+ WriteCategory(Settings::Category::Core);
qt_config->endGroup();
}
@@ -1248,7 +935,6 @@ void Config::SaveCoreValues() {
void Config::SaveDataStorageValues() {
qt_config->beginGroup(QStringLiteral("Data Storage"));
- WriteBasicSetting(Settings::values.use_virtual_sd);
WriteSetting(QStringLiteral("nand_directory"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
@@ -1265,9 +951,7 @@ void Config::SaveDataStorageValues() {
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
- WriteBasicSetting(Settings::values.gamecard_inserted);
- WriteBasicSetting(Settings::values.gamecard_current_game);
- WriteBasicSetting(Settings::values.gamecard_path);
+ WriteCategory(Settings::Category::DataStorage);
qt_config->endGroup();
}
@@ -1277,19 +961,9 @@ void Config::SaveDebuggingValues() {
// 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);
- WriteBasicSetting(Settings::values.use_gdbstub);
- WriteBasicSetting(Settings::values.gdbstub_port);
- WriteBasicSetting(Settings::values.program_args);
- WriteBasicSetting(Settings::values.dump_exefs);
- WriteBasicSetting(Settings::values.dump_nso);
- WriteBasicSetting(Settings::values.enable_fs_access_log);
- WriteBasicSetting(Settings::values.quest_flag);
- WriteBasicSetting(Settings::values.use_debug_asserts);
- WriteBasicSetting(Settings::values.disable_macro_jit);
- WriteBasicSetting(Settings::values.disable_macro_hle);
- WriteBasicSetting(Settings::values.enable_all_controllers);
- WriteBasicSetting(Settings::values.create_crash_dumps);
- WriteBasicSetting(Settings::values.perform_vulkan_check);
+
+ WriteCategory(Settings::Category::Debugging);
+ WriteCategory(Settings::Category::DebuggingGraphics);
qt_config->endGroup();
}
@@ -1297,7 +971,7 @@ void Config::SaveDebuggingValues() {
void Config::SaveNetworkValues() {
qt_config->beginGroup(QStringLiteral("Services"));
- WriteBasicSetting(Settings::values.network_interface);
+ WriteCategory(Settings::Category::Network);
qt_config->endGroup();
}
@@ -1324,8 +998,7 @@ void Config::SaveDisabledAddOnValues() {
void Config::SaveMiscellaneousValues() {
qt_config->beginGroup(QStringLiteral("Miscellaneous"));
- WriteBasicSetting(Settings::values.log_filter);
- WriteBasicSetting(Settings::values.use_dev_keys);
+ WriteCategory(Settings::Category::Miscellaneous);
qt_config->endGroup();
}
@@ -1353,34 +1026,9 @@ void Config::SavePathValues() {
void Config::SaveCpuValues() {
qt_config->beginGroup(QStringLiteral("Cpu"));
- WriteBasicSetting(Settings::values.cpu_accuracy_first_time);
- WriteSetting(QStringLiteral("cpu_accuracy"),
- static_cast<u32>(Settings::values.cpu_accuracy.GetValue(global)),
- static_cast<u32>(Settings::values.cpu_accuracy.GetDefault()),
- Settings::values.cpu_accuracy.UsingGlobal());
-
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_unfuse_fma);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_reduce_fp_error);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_inaccurate_nan);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_fastmem_check);
- WriteGlobalSetting(Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
- if (global) {
- WriteBasicSetting(Settings::values.cpu_debug_mode);
- WriteBasicSetting(Settings::values.cpuopt_page_tables);
- WriteBasicSetting(Settings::values.cpuopt_block_linking);
- WriteBasicSetting(Settings::values.cpuopt_return_stack_buffer);
- WriteBasicSetting(Settings::values.cpuopt_fast_dispatcher);
- WriteBasicSetting(Settings::values.cpuopt_context_elimination);
- WriteBasicSetting(Settings::values.cpuopt_const_prop);
- WriteBasicSetting(Settings::values.cpuopt_misc_ir);
- WriteBasicSetting(Settings::values.cpuopt_reduce_misalign_checks);
- WriteBasicSetting(Settings::values.cpuopt_fastmem);
- WriteBasicSetting(Settings::values.cpuopt_fastmem_exclusives);
- WriteBasicSetting(Settings::values.cpuopt_recompile_exclusives);
- WriteBasicSetting(Settings::values.cpuopt_ignore_memory_aborts);
- }
+ WriteCategory(Settings::Category::Cpu);
+ WriteCategory(Settings::Category::CpuDebug);
+ WriteCategory(Settings::Category::CpuUnsafe);
qt_config->endGroup();
}
@@ -1388,76 +1036,9 @@ void Config::SaveCpuValues() {
void Config::SaveRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
- WriteSetting(QString::fromStdString(Settings::values.renderer_backend.GetLabel()),
- static_cast<u32>(Settings::values.renderer_backend.GetValue(global)),
- static_cast<u32>(Settings::values.renderer_backend.GetDefault()),
- Settings::values.renderer_backend.UsingGlobal());
- WriteGlobalSetting(Settings::values.async_presentation);
- WriteGlobalSetting(Settings::values.renderer_force_max_clock);
- WriteGlobalSetting(Settings::values.vulkan_device);
- WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()),
- static_cast<u32>(Settings::values.fullscreen_mode.GetValue(global)),
- static_cast<u32>(Settings::values.fullscreen_mode.GetDefault()),
- Settings::values.fullscreen_mode.UsingGlobal());
- WriteGlobalSetting(Settings::values.aspect_ratio);
- WriteSetting(QString::fromStdString(Settings::values.resolution_setup.GetLabel()),
- static_cast<u32>(Settings::values.resolution_setup.GetValue(global)),
- static_cast<u32>(Settings::values.resolution_setup.GetDefault()),
- Settings::values.resolution_setup.UsingGlobal());
- WriteSetting(QString::fromStdString(Settings::values.scaling_filter.GetLabel()),
- static_cast<u32>(Settings::values.scaling_filter.GetValue(global)),
- static_cast<u32>(Settings::values.scaling_filter.GetDefault()),
- Settings::values.scaling_filter.UsingGlobal());
- WriteSetting(QString::fromStdString(Settings::values.fsr_sharpening_slider.GetLabel()),
- static_cast<u32>(Settings::values.fsr_sharpening_slider.GetValue(global)),
- static_cast<u32>(Settings::values.fsr_sharpening_slider.GetDefault()),
- Settings::values.fsr_sharpening_slider.UsingGlobal());
- WriteSetting(QString::fromStdString(Settings::values.anti_aliasing.GetLabel()),
- static_cast<u32>(Settings::values.anti_aliasing.GetValue(global)),
- static_cast<u32>(Settings::values.anti_aliasing.GetDefault()),
- Settings::values.anti_aliasing.UsingGlobal());
- WriteGlobalSetting(Settings::values.max_anisotropy);
- WriteGlobalSetting(Settings::values.speed_limit);
- WriteGlobalSetting(Settings::values.use_disk_shader_cache);
- WriteSetting(QString::fromStdString(Settings::values.gpu_accuracy.GetLabel()),
- static_cast<u32>(Settings::values.gpu_accuracy.GetValue(global)),
- static_cast<u32>(Settings::values.gpu_accuracy.GetDefault()),
- Settings::values.gpu_accuracy.UsingGlobal());
- WriteGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
- WriteSetting(QString::fromStdString(Settings::values.nvdec_emulation.GetLabel()),
- static_cast<u32>(Settings::values.nvdec_emulation.GetValue(global)),
- static_cast<u32>(Settings::values.nvdec_emulation.GetDefault()),
- Settings::values.nvdec_emulation.UsingGlobal());
- WriteGlobalSetting(Settings::values.accelerate_astc);
- WriteGlobalSetting(Settings::values.async_astc);
- WriteSetting(QString::fromStdString(Settings::values.astc_recompression.GetLabel()),
- static_cast<u32>(Settings::values.astc_recompression.GetValue(global)),
- static_cast<u32>(Settings::values.astc_recompression.GetDefault()),
- Settings::values.astc_recompression.UsingGlobal());
- WriteGlobalSetting(Settings::values.use_reactive_flushing);
- WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()),
- static_cast<u32>(Settings::values.shader_backend.GetValue(global)),
- static_cast<u32>(Settings::values.shader_backend.GetDefault()),
- Settings::values.shader_backend.UsingGlobal());
- WriteGlobalSetting(Settings::values.use_asynchronous_shaders);
- WriteGlobalSetting(Settings::values.use_fast_gpu_time);
- WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
- WriteGlobalSetting(Settings::values.enable_compute_pipelines);
- WriteGlobalSetting(Settings::values.use_video_framerate);
- WriteGlobalSetting(Settings::values.barrier_feedback_loops);
- WriteGlobalSetting(Settings::values.bg_red);
- WriteGlobalSetting(Settings::values.bg_green);
- WriteGlobalSetting(Settings::values.bg_blue);
-
- if (global) {
- WriteSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()),
- static_cast<u32>(Settings::values.vsync_mode.GetValue()),
- static_cast<u32>(Settings::values.vsync_mode.GetDefault()));
- WriteBasicSetting(Settings::values.renderer_debug);
- WriteBasicSetting(Settings::values.renderer_shader_feedback);
- WriteBasicSetting(Settings::values.enable_nsight_aftermath);
- WriteBasicSetting(Settings::values.disable_shader_loop_safety_checks);
- }
+ WriteCategory(Settings::Category::Renderer);
+ WriteCategory(Settings::Category::RendererAdvanced);
+ WriteCategory(Settings::Category::RendererDebug);
qt_config->endGroup();
}
@@ -1465,9 +1046,9 @@ void Config::SaveRendererValues() {
void Config::SaveScreenshotValues() {
qt_config->beginGroup(QStringLiteral("Screenshots"));
- WriteBasicSetting(UISettings::values.enable_screenshot_save_as);
WriteSetting(QStringLiteral("screenshot_path"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)));
+ WriteCategory(Settings::Category::Screenshots);
qt_config->endGroup();
}
@@ -1498,27 +1079,8 @@ void Config::SaveShortcutValues() {
void Config::SaveSystemValues() {
qt_config->beginGroup(QStringLiteral("System"));
- WriteGlobalSetting(Settings::values.language_index);
- WriteGlobalSetting(Settings::values.region_index);
- WriteGlobalSetting(Settings::values.time_zone_index);
-
- WriteSetting(QStringLiteral("rng_seed_enabled"),
- Settings::values.rng_seed.GetValue(global).has_value(), false,
- Settings::values.rng_seed.UsingGlobal());
- WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.GetValue(global).value_or(0),
- 0, Settings::values.rng_seed.UsingGlobal());
-
- if (global) {
- WriteBasicSetting(Settings::values.current_user);
-
- WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(),
- false);
- WriteSetting(QStringLiteral("custom_rtc"),
- QVariant::fromValue<long long>(Settings::values.custom_rtc.value_or(0)), 0);
- WriteBasicSetting(Settings::values.device_name);
- }
-
- WriteGlobalSetting(Settings::values.sound_index);
+ WriteCategory(Settings::Category::System);
+ WriteCategory(Settings::Category::SystemAudio);
qt_config->endGroup();
}
@@ -1526,10 +1088,11 @@ void Config::SaveSystemValues() {
void Config::SaveUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
+ WriteCategory(Settings::Category::Ui);
+ WriteCategory(Settings::Category::UiGeneral);
+
WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second));
- WriteBasicSetting(UISettings::values.enable_discord_presence);
- WriteBasicSetting(UISettings::values.select_user_on_boot);
SaveUIGamelistValues();
SaveUILayoutValues();
@@ -1538,37 +1101,14 @@ void Config::SaveUIValues() {
SaveShortcutValues();
SaveMultiplayerValues();
- WriteBasicSetting(UISettings::values.single_window_mode);
- WriteBasicSetting(UISettings::values.fullscreen);
- WriteBasicSetting(UISettings::values.display_titlebar);
- WriteBasicSetting(UISettings::values.show_filter_bar);
- WriteBasicSetting(UISettings::values.show_status_bar);
- WriteBasicSetting(UISettings::values.confirm_before_closing);
- WriteBasicSetting(UISettings::values.first_start);
- WriteBasicSetting(UISettings::values.callout_flags);
- WriteBasicSetting(UISettings::values.show_console);
- WriteBasicSetting(UISettings::values.pause_when_in_background);
- WriteBasicSetting(UISettings::values.mute_when_in_background);
- WriteBasicSetting(UISettings::values.hide_mouse);
- WriteBasicSetting(UISettings::values.controller_applet_disabled);
- WriteBasicSetting(UISettings::values.disable_web_applet);
-
qt_config->endGroup();
}
void Config::SaveUIGamelistValues() {
qt_config->beginGroup(QStringLiteral("UIGameList"));
- WriteBasicSetting(UISettings::values.show_add_ons);
- WriteBasicSetting(UISettings::values.show_compat);
- WriteBasicSetting(UISettings::values.show_size);
- WriteBasicSetting(UISettings::values.show_types);
- WriteBasicSetting(UISettings::values.game_icon_size);
- WriteBasicSetting(UISettings::values.folder_icon_size);
- WriteBasicSetting(UISettings::values.row_1_text_id);
- WriteBasicSetting(UISettings::values.row_2_text_id);
- WriteBasicSetting(UISettings::values.cache_game_list);
- WriteBasicSetting(UISettings::values.favorites_expanded);
+ WriteCategory(Settings::Category::UiGameList);
+
qt_config->beginWriteArray(QStringLiteral("favorites"));
for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
qt_config->setArrayIndex(i);
@@ -1589,7 +1129,8 @@ void Config::SaveUILayoutValues() {
WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
WriteSetting(QStringLiteral("microProfileDialogGeometry"),
UISettings::values.microprofile_geometry);
- WriteBasicSetting(UISettings::values.microprofile_visible);
+
+ WriteCategory(Settings::Category::UiLayout);
qt_config->endGroup();
}
@@ -1597,10 +1138,7 @@ void Config::SaveUILayoutValues() {
void Config::SaveWebServiceValues() {
qt_config->beginGroup(QStringLiteral("WebService"));
- WriteBasicSetting(Settings::values.enable_telemetry);
- WriteBasicSetting(Settings::values.web_api_url);
- WriteBasicSetting(Settings::values.yuzu_username);
- WriteBasicSetting(Settings::values.yuzu_token);
+ WriteCategory(Settings::Category::WebService);
qt_config->endGroup();
}
@@ -1608,17 +1146,7 @@ void Config::SaveWebServiceValues() {
void Config::SaveMultiplayerValues() {
qt_config->beginGroup(QStringLiteral("Multiplayer"));
- WriteBasicSetting(UISettings::values.multiplayer_nickname);
- WriteBasicSetting(UISettings::values.multiplayer_ip);
- WriteBasicSetting(UISettings::values.multiplayer_port);
- WriteBasicSetting(UISettings::values.multiplayer_room_nickname);
- WriteBasicSetting(UISettings::values.multiplayer_room_name);
- WriteBasicSetting(UISettings::values.multiplayer_room_port);
- WriteBasicSetting(UISettings::values.multiplayer_host_type);
- WriteBasicSetting(UISettings::values.multiplayer_port);
- WriteBasicSetting(UISettings::values.multiplayer_max_player);
- WriteBasicSetting(UISettings::values.multiplayer_game_id);
- WriteBasicSetting(UISettings::values.multiplayer_room_description);
+ WriteCategory(Settings::Category::Multiplayer);
// Write ban list
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
@@ -1653,27 +1181,6 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
return result;
}
-template <typename Type, bool ranged>
-void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
- QString name = QString::fromStdString(setting.GetLabel());
- const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
- setting.SetGlobal(use_global);
- if (global || !use_global) {
- setting.SetValue(static_cast<QVariant>(
- ReadSetting(name, QVariant::fromValue<Type>(setting.GetDefault())))
- .value<Type>());
- }
-}
-
-template <typename Type>
-void Config::ReadSettingGlobal(Type& setting, const QString& name,
- const QVariant& default_value) const {
- const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
- if (global || !use_global) {
- setting = ReadSetting(name, default_value).value<Type>();
- }
-}
-
void Config::WriteSetting(const QString& name, const QVariant& value) {
qt_config->setValue(name, value);
}
@@ -1727,3 +1234,72 @@ void Config::ClearControlPlayerValues() {
const std::string& Config::GetConfigFilePath() const {
return qt_config_loc;
}
+
+static auto FindRelevantList(Settings::Category category) {
+ auto& map = Settings::values.linkage.by_category;
+ if (map.contains(category)) {
+ return Settings::values.linkage.by_category[category];
+ }
+ return UISettings::values.linkage.by_category[category];
+}
+
+void Config::ReadCategory(Settings::Category category) {
+ const auto& settings = FindRelevantList(category);
+ std::for_each(settings.begin(), settings.end(),
+ [&](const auto& setting) { ReadSettingGeneric(setting); });
+}
+
+void Config::WriteCategory(Settings::Category category) {
+ const auto& settings = FindRelevantList(category);
+ std::for_each(settings.begin(), settings.end(),
+ [&](const auto& setting) { WriteSettingGeneric(setting); });
+}
+
+void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
+ if (!setting->Save() || (!setting->Switchable() && !global)) {
+ return;
+ }
+ const QString name = QString::fromStdString(setting->GetLabel());
+ const auto default_value =
+ QVariant::fromValue<QString>(QString::fromStdString(setting->DefaultToString()));
+
+ bool use_global = true;
+ if (setting->Switchable() && !global) {
+ use_global = qt_config->value(name + QStringLiteral("/use_global"), true).value<bool>();
+ setting->SetGlobal(use_global);
+ }
+
+ if (global || !use_global) {
+ const bool is_default =
+ qt_config->value(name + QStringLiteral("/default"), true).value<bool>();
+ if (!is_default) {
+ setting->LoadString(
+ qt_config->value(name, default_value).value<QString>().toStdString());
+ } else {
+ // Empty string resets the Setting to default
+ setting->LoadString("");
+ }
+ }
+}
+
+void Config::WriteSettingGeneric(Settings::BasicSetting* const setting) const {
+ if (!setting->Save()) {
+ return;
+ }
+ const QVariant value = QVariant::fromValue(QString::fromStdString(setting->ToString()));
+ const QVariant default_value =
+ QVariant::fromValue(QString::fromStdString(setting->DefaultToString()));
+ const QString label = QString::fromStdString(setting->GetLabel());
+ if (setting->Switchable()) {
+ if (!global) {
+ qt_config->setValue(label + QStringLiteral("/use_global"), setting->UsingGlobal());
+ }
+ if (global || !setting->UsingGlobal()) {
+ qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
+ qt_config->setValue(label, value);
+ }
+ } else if (global) {
+ qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
+ qt_config->setValue(label, value);
+ }
+}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 1211389d2..727feebfb 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -9,6 +9,7 @@
#include <QMetaType>
#include <QVariant>
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "yuzu/uisettings.h"
class QSettings;
@@ -51,8 +52,8 @@ public:
static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map;
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map;
- static const std::map<bool, QString> use_docked_mode_texts_map;
- static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
+ static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map;
+ static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map;
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
@@ -74,7 +75,6 @@ private:
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
- void ReadMousePanningValues();
void ReadMotionTouchValues();
void ReadHidbusValues();
void ReadIrCameraValues();
@@ -99,13 +99,13 @@ private:
void ReadUILayoutValues();
void ReadWebServiceValues();
void ReadMultiplayerValues();
+ void ReadNetworkValues();
void SaveValues();
void SavePlayerValue(std::size_t player_index);
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
- void SaveMousePanningValues();
void SaveMotionTouchValues();
void SaveHidbusValues();
void SaveIrCameraValues();
@@ -141,18 +141,6 @@ private:
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
/**
- * Only reads a setting from the qt_config if the current config is a global config, or if the
- * current config is a custom config and the setting is overriding the global setting. Otherwise
- * it does nothing.
- *
- * @param setting The variable to be modified
- * @param name The setting's identifier
- * @param default_value The value to use when the setting is not already present in the config
- */
- template <typename Type>
- void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
-
- /**
* Writes a setting to the qt_config.
*
* @param name The setting's idetentifier
@@ -166,50 +154,20 @@ private:
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
bool use_global);
- /**
- * Reads a value from the qt_config and applies it to the setting, using its label and default
- * value. If the config is a custom config, this will also read the global state of the setting
- * and apply that information to it.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting);
-
- /**
- * Sets a value to the qt_config using the setting's label and default value. If the config is a
- * custom config, it will apply the global state, and the custom value if needed.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting);
-
- /**
- * Reads a value from the qt_config using the setting's label and default value and applies the
- * value to the setting.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void ReadBasicSetting(Settings::Setting<Type, ranged>& setting);
-
- /** Sets a value from the setting in the qt_config using the setting's label and default value.
- *
- * @param The setting
- */
- template <typename Type, bool ranged>
- void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting);
+ void ReadCategory(Settings::Category category);
+ void WriteCategory(Settings::Category category);
+ void ReadSettingGeneric(Settings::BasicSetting* const setting);
+ void WriteSettingGeneric(Settings::BasicSetting* const setting) const;
- ConfigType type;
+ const ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
- bool global;
+ const bool global;
};
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
-Q_DECLARE_METATYPE(Settings::CPUAccuracy);
-Q_DECLARE_METATYPE(Settings::GPUAccuracy);
+Q_DECLARE_METATYPE(Settings::CpuAccuracy);
+Q_DECLARE_METATYPE(Settings::GpuAccuracy);
Q_DECLARE_METATYPE(Settings::FullscreenMode);
Q_DECLARE_METATYPE(Settings::NvdecEmulation);
Q_DECLARE_METATYPE(Settings::ResolutionSetup);
@@ -218,3 +176,4 @@ Q_DECLARE_METATYPE(Settings::AntiAliasing);
Q_DECLARE_METATYPE(Settings::RendererBackend);
Q_DECLARE_METATYPE(Settings::ShaderBackend);
Q_DECLARE_METATYPE(Settings::AstcRecompression);
+Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
index ac42cc7fc..0ed6146a0 100644
--- a/src/yuzu/configuration/configuration_shared.cpp
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -1,104 +1,19 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <QCheckBox>
-#include <QObject>
-#include <QString>
-#include "common/settings.h"
+#include <memory>
+#include <type_traits>
+#include <vector>
#include "yuzu/configuration/configuration_shared.h"
-#include "yuzu/configuration/configure_per_game.h"
-void ConfigurationShared::ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting,
- const QCheckBox* checkbox,
- const CheckState& tracker) {
- if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
- setting->SetValue(checkbox->checkState());
- } else if (!Settings::IsConfiguringGlobal()) {
- if (tracker == CheckState::Global) {
- setting->SetGlobal(true);
- } else {
- setting->SetGlobal(false);
- setting->SetValue(checkbox->checkState());
- }
- }
-}
+namespace ConfigurationShared {
-void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
- const Settings::SwitchableSetting<bool>* setting) {
- if (setting->UsingGlobal()) {
- checkbox->setCheckState(Qt::PartiallyChecked);
- } else {
- checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
+Tab::Tab(std::shared_ptr<std::vector<Tab*>> group, QWidget* parent) : QWidget(parent) {
+ if (group != nullptr) {
+ group->push_back(this);
}
}
-void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
- if (highlighted) {
- widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
- .arg(widget->objectName()));
- } else {
- widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
- .arg(widget->objectName()));
- }
- widget->show();
-}
+Tab::~Tab() = default;
-void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
- const Settings::SwitchableSetting<bool>& setting,
- CheckState& tracker) {
- if (setting.UsingGlobal()) {
- tracker = CheckState::Global;
- } else {
- tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
- tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
- static_cast<int>(CheckState::Count));
- if (tracker == CheckState::Global) {
- checkbox->setChecked(setting.GetValue(true));
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- });
-}
-
-void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
- bool global_state, CheckState& tracker) {
- if (global) {
- tracker = CheckState::Global;
- } else {
- tracker = (state == global_state) ? CheckState::On : CheckState::Off;
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
- tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
- static_cast<int>(CheckState::Count));
- if (tracker == CheckState::Global) {
- checkbox->setChecked(global_state);
- }
- SetHighlight(checkbox, tracker != CheckState::Global);
- });
-}
-
-void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
- InsertGlobalItem(combobox, global);
- QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
- [target](int index) { SetHighlight(target, index != 0); });
-}
-
-void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
- const QString use_global_text =
- ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index));
- combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
- combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
-}
-
-int ConfigurationShared::GetComboboxIndex(int global_setting_index, const QComboBox* combobox) {
- if (Settings::IsConfiguringGlobal()) {
- return combobox->currentIndex();
- }
- if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- return global_setting_index;
- }
- return combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET;
-}
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
index 04c88758c..31897a6b0 100644
--- a/src/yuzu/configuration/configuration_shared.h
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -3,73 +3,25 @@
#pragma once
-#include <QCheckBox>
-#include <QComboBox>
-#include "common/settings.h"
+#include <memory>
+#include <vector>
+#include <QString>
+#include <QWidget>
+#include <qobjectdefs.h>
-namespace ConfigurationShared {
-
-constexpr int USE_GLOBAL_INDEX = 0;
-constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
-constexpr int USE_GLOBAL_OFFSET = 2;
-
-// CheckBoxes require a tracker for their state since we emulate a tristate CheckBox
-enum class CheckState {
- Off, // Checkbox overrides to off/false
- On, // Checkbox overrides to on/true
- Global, // Checkbox defers to the global state
- Count, // Simply the number of states, not a valid checkbox state
-};
-
-// Global-aware apply and set functions
-
-// ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting
-void ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting, const QCheckBox* checkbox,
- const CheckState& tracker);
-template <typename Type, bool ranged>
-void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
- const QComboBox* combobox) {
- if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
- setting->SetValue(static_cast<Type>(combobox->currentIndex()));
- } else if (!Settings::IsConfiguringGlobal()) {
- if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- setting->SetGlobal(true);
- } else {
- setting->SetGlobal(false);
- setting->SetValue(static_cast<Type>(combobox->currentIndex() -
- ConfigurationShared::USE_GLOBAL_OFFSET));
- }
- }
-}
+class QObject;
-// Sets a Qt UI element given a Settings::Setting
-void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>* setting);
-
-template <typename Type, bool ranged>
-void SetPerGameSetting(QComboBox* combobox,
- const Settings::SwitchableSetting<Type, ranged>* setting) {
- combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
- : static_cast<int>(setting->GetValue()) +
- ConfigurationShared::USE_GLOBAL_OFFSET);
-}
-
-// (Un)highlights a Qt UI element
-void SetHighlight(QWidget* widget, bool highlighted);
-
-// Sets up a QCheckBox like a tristate one, given a Setting
-void SetColoredTristate(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>& setting,
- CheckState& tracker);
-void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
- CheckState& tracker);
+namespace ConfigurationShared {
-// Sets up coloring of a QWidget `target` based on the state of a QComboBox, and calls
-// InsertGlobalItem
-void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
+class Tab : public QWidget {
+ Q_OBJECT
-// Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox
-void InsertGlobalItem(QComboBox* combobox, int global_index);
+public:
+ explicit Tab(std::shared_ptr<std::vector<Tab*>> group, QWidget* parent = nullptr);
+ ~Tab();
-// Returns the correct index of a QComboBox taking into account global configuration
-int GetComboboxIndex(int global_setting_index, const QComboBox* combobox);
+ virtual void ApplyConfiguration() = 0;
+ virtual void SetConfiguration() = 0;
+};
} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index eb8078467..573c40801 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -48,11 +48,34 @@
</layout>
</item>
<item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </widget>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Some settings are only available when a game is not running.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index fcd6d61a0..81dd51ad3 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -1,87 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <map>
#include <memory>
+#include <vector>
+#include <QComboBox>
#include "audio_core/sink/sink.h"
#include "audio_core/sink/sink_details.h"
+#include "common/common_types.h"
#include "common/settings.h"
+#include "common/settings_common.h"
#include "core/core.h"
#include "ui_configure_audio.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
+#include "yuzu/configuration/shared_translation.h"
+#include "yuzu/configuration/shared_widget.h"
#include "yuzu/uisettings.h"
-ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
+ConfigureAudio::ConfigureAudio(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
ui->setupUi(this);
+ Setup(builder);
- InitializeAudioSinkComboBox();
+ SetConfiguration();
+}
- connect(ui->volume_slider, &QSlider::valueChanged, this,
- &ConfigureAudio::SetVolumeIndicatorText);
- connect(ui->sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
- &ConfigureAudio::UpdateAudioDevices);
+ConfigureAudio::~ConfigureAudio() = default;
- ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
- ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
+void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
+ auto& layout = *ui->audio_widget->layout();
- SetupPerGameUI();
+ std::vector<Settings::BasicSetting*> settings;
- SetConfiguration();
+ std::map<u32, QWidget*> hold;
- const bool is_powered_on = system_.IsPoweredOn();
- ui->sink_combo_box->setEnabled(!is_powered_on);
- ui->output_combo_box->setEnabled(!is_powered_on);
- ui->input_combo_box->setEnabled(!is_powered_on);
-}
+ auto push = [&](Settings::Category category) {
+ for (auto* setting : Settings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
+ for (auto* setting : UISettings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
+ };
-ConfigureAudio::~ConfigureAudio() = default;
+ push(Settings::Category::Audio);
+ push(Settings::Category::SystemAudio);
-void ConfigureAudio::SetConfiguration() {
- SetOutputSinkFromSinkID();
+ for (auto* setting : settings) {
+ auto* widget = builder.BuildWidget(setting, apply_funcs);
- // The device list cannot be pre-populated (nor listed) until the output sink is known.
- UpdateAudioDevices(ui->sink_combo_box->currentIndex());
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
- SetAudioDevicesFromDeviceID();
+ hold.emplace(std::pair{setting->Id(), widget});
+
+ if (setting->Id() == Settings::values.sink_id.Id()) {
+ // TODO (lat9nq): Let the system manage sink_id
+ sink_combo_box = widget->combobox;
+ InitializeAudioSinkComboBox();
+
+ connect(sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureAudio::UpdateAudioDevices);
+ } else if (setting->Id() == Settings::values.audio_output_device_id.Id()) {
+ // Keep track of output (and input) device comboboxes to populate them with system
+ // devices, which are determined at run time
+ output_device_combo_box = widget->combobox;
+ } else if (setting->Id() == Settings::values.audio_input_device_id.Id()) {
+ input_device_combo_box = widget->combobox;
+ }
+ }
- const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
- ui->volume_slider->setValue(volume_value);
- ui->toggle_background_mute->setChecked(UISettings::values.mute_when_in_background.GetValue());
+ for (const auto& [id, widget] : hold) {
+ layout.addWidget(widget);
+ }
+}
+void ConfigureAudio::SetConfiguration() {
if (!Settings::IsConfiguringGlobal()) {
- if (Settings::values.volume.UsingGlobal()) {
- ui->volume_combo_box->setCurrentIndex(0);
- ui->volume_slider->setEnabled(false);
- } else {
- ui->volume_combo_box->setCurrentIndex(1);
- ui->volume_slider->setEnabled(true);
- }
- ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
- ConfigurationShared::SetHighlight(ui->mode_label,
- !Settings::values.sound_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->volume_layout,
- !Settings::values.volume.UsingGlobal());
- } else {
- ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue());
+ return;
}
- SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
+
+ SetOutputSinkFromSinkID();
+
+ // The device list cannot be pre-populated (nor listed) until the output sink is known.
+ UpdateAudioDevices(sink_combo_box->currentIndex());
+
+ SetAudioDevicesFromDeviceID();
}
void ConfigureAudio::SetOutputSinkFromSinkID() {
- [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box);
+ [[maybe_unused]] const QSignalBlocker blocker(sink_combo_box);
int new_sink_index = 0;
- const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
- for (int index = 0; index < ui->sink_combo_box->count(); index++) {
- if (ui->sink_combo_box->itemText(index) == sink_id) {
+ const QString sink_id = QString::fromStdString(Settings::values.sink_id.ToString());
+ for (int index = 0; index < sink_combo_box->count(); index++) {
+ if (sink_combo_box->itemText(index) == sink_id) {
new_sink_index = index;
break;
}
}
- ui->sink_combo_box->setCurrentIndex(new_sink_index);
+ sink_combo_box->setCurrentIndex(new_sink_index);
}
void ConfigureAudio::SetAudioDevicesFromDeviceID() {
@@ -89,57 +117,42 @@ void ConfigureAudio::SetAudioDevicesFromDeviceID() {
const QString output_device_id =
QString::fromStdString(Settings::values.audio_output_device_id.GetValue());
- for (int index = 0; index < ui->output_combo_box->count(); index++) {
- if (ui->output_combo_box->itemText(index) == output_device_id) {
+ for (int index = 0; index < output_device_combo_box->count(); index++) {
+ if (output_device_combo_box->itemText(index) == output_device_id) {
new_device_index = index;
break;
}
}
- ui->output_combo_box->setCurrentIndex(new_device_index);
+ output_device_combo_box->setCurrentIndex(new_device_index);
new_device_index = -1;
const QString input_device_id =
QString::fromStdString(Settings::values.audio_input_device_id.GetValue());
- for (int index = 0; index < ui->input_combo_box->count(); index++) {
- if (ui->input_combo_box->itemText(index) == input_device_id) {
+ for (int index = 0; index < input_device_combo_box->count(); index++) {
+ if (input_device_combo_box->itemText(index) == input_device_id) {
new_device_index = index;
break;
}
}
- ui->input_combo_box->setCurrentIndex(new_device_index);
-}
-
-void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
- ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
+ input_device_combo_box->setCurrentIndex(new_device_index);
}
void ConfigureAudio::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound);
+ const bool is_powered_on = system.IsPoweredOn();
+ for (const auto& apply_func : apply_funcs) {
+ apply_func(is_powered_on);
+ }
if (Settings::IsConfiguringGlobal()) {
- Settings::values.sink_id =
- ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString();
+ Settings::values.sink_id.LoadString(
+ sink_combo_box->itemText(sink_combo_box->currentIndex()).toStdString());
Settings::values.audio_output_device_id.SetValue(
- ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString());
+ output_device_combo_box->itemText(output_device_combo_box->currentIndex())
+ .toStdString());
Settings::values.audio_input_device_id.SetValue(
- ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString());
- UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked();
-
- // Guard if during game and set to game-specific value
- if (Settings::values.volume.UsingGlobal()) {
- const auto volume = static_cast<u8>(ui->volume_slider->value());
- Settings::values.volume.SetValue(volume);
- }
- } else {
- if (ui->volume_combo_box->currentIndex() == 0) {
- Settings::values.volume.SetGlobal(true);
- } else {
- Settings::values.volume.SetGlobal(false);
- const auto volume = static_cast<u8>(ui->volume_slider->value());
- Settings::values.volume.SetValue(volume);
- }
+ input_device_combo_box->itemText(input_device_combo_box->currentIndex()).toStdString());
}
}
@@ -152,54 +165,31 @@ void ConfigureAudio::changeEvent(QEvent* event) {
}
void ConfigureAudio::UpdateAudioDevices(int sink_index) {
- ui->output_combo_box->clear();
- ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ output_device_combo_box->clear();
+ output_device_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
- const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString();
+ const auto sink_id =
+ Settings::ToEnum<Settings::AudioEngine>(sink_combo_box->itemText(sink_index).toStdString());
for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) {
- ui->output_combo_box->addItem(QString::fromStdString(device));
+ output_device_combo_box->addItem(QString::fromStdString(device));
}
- ui->input_combo_box->clear();
- ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ input_device_combo_box->clear();
+ input_device_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) {
- ui->input_combo_box->addItem(QString::fromStdString(device));
+ input_device_combo_box->addItem(QString::fromStdString(device));
}
}
void ConfigureAudio::InitializeAudioSinkComboBox() {
- ui->sink_combo_box->clear();
- ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
+ sink_combo_box->clear();
+ sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
for (const auto& id : AudioCore::Sink::GetSinkIDs()) {
- ui->sink_combo_box->addItem(QString::fromUtf8(id.data(), static_cast<s32>(id.length())));
+ sink_combo_box->addItem(QString::fromStdString(Settings::CanonicalizeEnum(id)));
}
}
void ConfigureAudio::RetranslateUI() {
ui->retranslateUi(this);
- SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
-}
-
-void ConfigureAudio::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal());
- ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
- return;
- }
-
- ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->mode_label,
- Settings::values.sound_index.GetValue(true));
-
- connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
- ui->volume_slider->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
- });
-
- ui->sink_combo_box->setVisible(false);
- ui->sink_label->setVisible(false);
- ui->output_combo_box->setVisible(false);
- ui->output_label->setVisible(false);
- ui->input_combo_box->setVisible(false);
- ui->input_label->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 0d03aae1d..79538e81c 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -3,30 +3,35 @@
#pragma once
+#include <functional>
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+
+class QComboBox;
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureAudio;
}
-class ConfigureAudio : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureAudio : public ConfigurationShared::Tab {
public:
- explicit ConfigureAudio(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureAudio(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureAudio() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
@@ -39,11 +44,16 @@ private:
void SetOutputSinkFromSinkID();
void SetAudioDevicesFromDeviceID();
- void SetVolumeIndicatorText(int percentage);
- void SetupPerGameUI();
+ void Setup(const ConfigurationShared::Builder& builder);
std::unique_ptr<Ui::ConfigureAudio> ui;
const Core::System& system;
+
+ std::vector<std::function<void(bool)>> apply_funcs{};
+
+ QComboBox* sink_combo_box;
+ QComboBox* output_device_combo_box;
+ QComboBox* input_device_combo_box;
};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index 4128c83ad..1181aeb00 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -21,80 +21,14 @@
</property>
<layout class="QVBoxLayout">
<item>
- <layout class="QHBoxLayout" name="engine_layout">
- <item>
- <widget class="QLabel" name="sink_label">
- <property name="text">
- <string>Output Engine:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="sink_combo_box"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="output_layout">
- <item>
- <widget class="QLabel" name="output_label">
- <property name="text">
- <string>Output Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="output_combo_box"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="input_layout">
- <item>
- <widget class="QLabel" name="input_label">
- <property name="text">
- <string>Input Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="input_combo_box"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="mode_layout">
- <item>
- <widget class="QLabel" name="mode_label">
- <property name="text">
- <string>Sound Output Mode:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="combo_sound">
- <item>
- <property name="text">
- <string>Mono</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Stereo</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Surround</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QWidget" name="volume_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <widget class="QWidget" name="audio_widget" native="true">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777213</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -107,89 +41,9 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QComboBox" name="volume_combo_box">
- <item>
- <property name="text">
- <string>Use global volume</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set volume:</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="volume_label">
- <property name="text">
- <string>Volume:</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>30</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QSlider" name="volume_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximum">
- <number>200</number>
- </property>
- <property name="pageStep">
- <number>5</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="volume_indicator">
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>0 %</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
- <item>
- <layout class="QHBoxLayout" name="mute_layout">
- <item>
- <widget class="QCheckBox" name="toggle_background_mute">
- <property name="text">
- <string>Mute audio when in background</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index 3d69fb03f..a51359903 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -1,88 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <memory>
+#include <typeinfo>
+#include <vector>
+#include <QComboBox>
#include "common/common_types.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
+#include "configuration/shared_widget.h"
#include "core/core.h"
#include "ui_configure_cpu.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_cpu.h"
-ConfigureCpu::ConfigureCpu(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureCpu>()}, system{system_} {
+ConfigureCpu::ConfigureCpu(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureCpu>()}, system{system_},
+ combobox_translations(builder.ComboboxTranslations()) {
ui->setupUi(this);
- SetupPerGameUI();
+ Setup(builder);
SetConfiguration();
- connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ connect(accuracy_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureCpu::UpdateGroup);
}
ConfigureCpu::~ConfigureCpu() = default;
-void ConfigureCpu::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
-
- ui->accuracy->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_ignore_standard_fpcr->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock);
- ui->cpuopt_unsafe_ignore_global_monitor->setEnabled(runtime_lock);
-
- ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue());
- ui->cpuopt_unsafe_reduce_fp_error->setChecked(
- Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue());
- ui->cpuopt_unsafe_ignore_standard_fpcr->setChecked(
- Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue());
- ui->cpuopt_unsafe_inaccurate_nan->setChecked(
- Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue());
- ui->cpuopt_unsafe_fastmem_check->setChecked(
- Settings::values.cpuopt_unsafe_fastmem_check.GetValue());
- ui->cpuopt_unsafe_ignore_global_monitor->setChecked(
- Settings::values.cpuopt_unsafe_ignore_global_monitor.GetValue());
-
- if (Settings::IsConfiguringGlobal()) {
- ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue()));
- } else {
- ConfigurationShared::SetPerGameSetting(ui->accuracy, &Settings::values.cpu_accuracy);
- ConfigurationShared::SetHighlight(ui->widget_accuracy,
- !Settings::values.cpu_accuracy.UsingGlobal());
+void ConfigureCpu::SetConfiguration() {}
+void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
+ auto* accuracy_layout = ui->widget_accuracy->layout();
+ auto* unsafe_layout = ui->unsafe_widget->layout();
+ std::map<u32, QWidget*> unsafe_hold{};
+
+ std::vector<Settings::BasicSetting*> settings;
+ const auto push = [&](Settings::Category category) {
+ for (const auto setting : Settings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
+ };
+
+ push(Settings::Category::Cpu);
+ push(Settings::Category::CpuUnsafe);
+
+ for (const auto setting : settings) {
+ auto* widget = builder.BuildWidget(setting, apply_funcs);
+
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ if (setting->Id() == Settings::values.cpu_accuracy.Id()) {
+ // Keep track of cpu_accuracy combobox to display/hide the unsafe settings
+ accuracy_layout->addWidget(widget);
+ accuracy_combobox = widget->combobox;
+ } else {
+ // Presently, all other settings here are unsafe checkboxes
+ unsafe_hold.insert({setting->Id(), widget});
+ }
}
- UpdateGroup(ui->accuracy->currentIndex());
+
+ for (const auto& [label, widget] : unsafe_hold) {
+ unsafe_layout->addWidget(widget);
+ }
+
+ UpdateGroup(accuracy_combobox->currentIndex());
}
void ConfigureCpu::UpdateGroup(int index) {
- if (!Settings::IsConfiguringGlobal()) {
- index -= ConfigurationShared::USE_GLOBAL_OFFSET;
- }
- const auto accuracy = static_cast<Settings::CPUAccuracy>(index);
- ui->unsafe_group->setVisible(accuracy == Settings::CPUAccuracy::Unsafe);
+ const auto accuracy = static_cast<Settings::CpuAccuracy>(
+ combobox_translations.at(Settings::EnumMetadata<Settings::CpuAccuracy>::Index())[index]
+ .first);
+ ui->unsafe_group->setVisible(accuracy == Settings::CpuAccuracy::Unsafe);
}
void ConfigureCpu::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpu_accuracy, ui->accuracy);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_unfuse_fma,
- ui->cpuopt_unsafe_unfuse_fma,
- cpuopt_unsafe_unfuse_fma);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error,
- ui->cpuopt_unsafe_reduce_fp_error,
- cpuopt_unsafe_reduce_fp_error);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
- ui->cpuopt_unsafe_ignore_standard_fpcr,
- cpuopt_unsafe_ignore_standard_fpcr);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan,
- ui->cpuopt_unsafe_inaccurate_nan,
- cpuopt_unsafe_inaccurate_nan);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_fastmem_check,
- ui->cpuopt_unsafe_fastmem_check,
- cpuopt_unsafe_fastmem_check);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_ignore_global_monitor,
- ui->cpuopt_unsafe_ignore_global_monitor,
- cpuopt_unsafe_ignore_global_monitor);
+ const bool is_powered_on = system.IsPoweredOn();
+ for (const auto& apply_func : apply_funcs) {
+ apply_func(is_powered_on);
+ }
}
void ConfigureCpu::changeEvent(QEvent* event) {
@@ -96,32 +100,3 @@ void ConfigureCpu::changeEvent(QEvent* event) {
void ConfigureCpu::RetranslateUI() {
ui->retranslateUi(this);
}
-
-void ConfigureCpu::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- return;
- }
-
- ConfigurationShared::SetColoredComboBox(
- ui->accuracy, ui->widget_accuracy,
- static_cast<u32>(Settings::values.cpu_accuracy.GetValue(true)));
-
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_unfuse_fma,
- Settings::values.cpuopt_unsafe_unfuse_fma,
- cpuopt_unsafe_unfuse_fma);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error,
- Settings::values.cpuopt_unsafe_reduce_fp_error,
- cpuopt_unsafe_reduce_fp_error);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_standard_fpcr,
- Settings::values.cpuopt_unsafe_ignore_standard_fpcr,
- cpuopt_unsafe_ignore_standard_fpcr);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan,
- Settings::values.cpuopt_unsafe_inaccurate_nan,
- cpuopt_unsafe_inaccurate_nan);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_fastmem_check,
- Settings::values.cpuopt_unsafe_fastmem_check,
- cpuopt_unsafe_fastmem_check);
- ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_ignore_global_monitor,
- Settings::values.cpuopt_unsafe_ignore_global_monitor,
- cpuopt_unsafe_ignore_global_monitor);
-}
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index 86d928ca3..61a6de7aa 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -4,29 +4,34 @@
#pragma once
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/shared_translation.h"
+
+class QComboBox;
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureCpu;
}
-class ConfigureCpu : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureCpu : public ConfigurationShared::Tab {
public:
- explicit ConfigureCpu(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureCpu(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureCpu() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
@@ -34,16 +39,14 @@ private:
void UpdateGroup(int index);
- void SetupPerGameUI();
+ void Setup(const ConfigurationShared::Builder& builder);
std::unique_ptr<Ui::ConfigureCpu> ui;
- ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma;
- ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error;
- ConfigurationShared::CheckState cpuopt_unsafe_ignore_standard_fpcr;
- ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan;
- ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check;
- ConfigurationShared::CheckState cpuopt_unsafe_ignore_global_monitor;
-
const Core::System& system;
+
+ const ConfigurationShared::ComboboxTranslationMap& combobox_translations;
+ std::vector<std::function<void(bool)>> apply_funcs{};
+
+ QComboBox* accuracy_combobox;
};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index 8ae569ee6..f734e842e 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -16,9 +16,12 @@
<property name="accessibleName">
<string>CPU</string>
</property>
- <layout class="QVBoxLayout">
+ <layout class="QVBoxLayout" name="vboxlayout_2" stretch="0">
<item>
- <layout class="QVBoxLayout">
+ <layout class="QVBoxLayout" name="vboxlayout">
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@@ -27,38 +30,19 @@
<layout class="QVBoxLayout">
<item>
<widget class="QWidget" name="widget_accuracy" native="true">
- <layout class="QHBoxLayout" name="layout_accuracy">
- <item>
- <widget class="QLabel" name="label_accuracy">
- <property name="text">
- <string>Accuracy:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="accuracy">
- <item>
- <property name="text">
- <string>Auto</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Accurate</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Unsafe</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Paranoid (disables most optimizations)</string>
- </property>
- </item>
- </widget>
- </item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
</layout>
</widget>
</item>
@@ -75,10 +59,6 @@
</layout>
</widget>
</item>
- </layout>
- </item>
- <item>
- <layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="unsafe_group">
<property name="title">
@@ -96,105 +76,44 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Unfuse FMA (improve performance on CPUs without FMA)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Faster FRSQRTE and FRECPE</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_ignore_standard_fpcr">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Faster ASIMD instructions (32 bits only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Inaccurate NaN handling</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_fastmem_check">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Disable address space checks</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="cpuopt_unsafe_ignore_global_monitor">
- <property name="toolTip">
- <string>
- &lt;div&gt;This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions.&lt;/div&gt;
- </string>
- </property>
- <property name="text">
- <string>Ignore global monitor</string>
- </property>
+ <widget class="QWidget" name="unsafe_widget" native="true">
+ <layout class="QVBoxLayout" name="unsafe_layout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
</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>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </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>
- <item>
- <widget class="QLabel" name="label_disable_info">
- <property name="text">
- <string>CPU settings are available only when game is not running.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
</layout>
</widget>
<resources/>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index cbeb8f168..b22fda746 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() {
ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
+ ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
+ ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
ui->enable_graphics_debugging->setEnabled(runtime_lock);
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
+ Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 15acefe33..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -2,360 +2,556 @@
<ui version="4.0">
<class>ConfigureDebug</class>
<widget class="QScrollArea" name="ConfigureDebug">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>831</width>
+ <height>760</height>
+ </rect>
+ </property>
<property name="widgetResizable">
<bool>true</bool>
</property>
- <widget class="QWidget">
- <layout class="QVBoxLayout" name="verticalLayout_1">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Debugger</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_11">
- <item>
- <widget class="QCheckBox" name="toggle_gdbstub">
- <property name="text">
- <string>Enable GDB Stub</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <widget class="QWidget" name="widget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>842</width>
+ <height>741</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_1">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Debugger</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QWidget" name="debug_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
+ <property name="leftMargin">
+ <number>0</number>
</property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_11">
- <property name="text">
- <string>Port:</string>
+ <property name="topMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="gdbport_spinbox">
- <property name="minimum">
- <number>1024</number>
+ <property name="rightMargin">
+ <number>0</number>
</property>
- <property name="maximum">
- <number>65535</number>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Logging</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_1">
- <item row="0" column="0" colspan="2">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <item>
- <widget class="QLabel" name="label_1">
- <property name="text">
- <string>Global Log Filter</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="log_filter_edit"/>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="toggle_console">
- <property name="text">
- <string>Show Log in Console</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QPushButton" name="open_log_button">
- <property name="text">
- <string>Open Log Location</string>
- </property>
+ <item>
+ <widget class="QCheckBox" name="toggle_gdbstub">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Enable GDB Stub</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="horizontalWidget_3" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="gdbport_spinbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <number>1024</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="extended_logging">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
- </property>
- <property name="text">
- <string>Enable Extended Logging**</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Homebrew</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Arguments String</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="homebrew_args_edit"/>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_4">
- <property name="title">
- <string>Graphics</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QCheckBox" name="enable_graphics_debugging">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, the graphics API enters a slower debugging mode</string>
- </property>
- <property name="text">
- <string>Enable Graphics Debugging</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="enable_nsight_aftermath">
- <property name="toolTip">
- <string>When checked, it enables Nsight Aftermath crash dumps</string>
- </property>
- <property name="text">
- <string>Enable Nsight Aftermath</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="dump_shaders">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
- </property>
- <property name="text">
- <string>Dump Game Shaders</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QCheckBox" name="dump_macros">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it will dump all the macro programs of the GPU</string>
- </property>
- <property name="text">
- <string>Dump Maxwell Macros</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="disable_macro_jit">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
- </property>
- <property name="text">
- <string>Disable Macro JIT</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QCheckBox" name="disable_macro_hle">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
- </property>
- <property name="text">
- <string>Disable Macro HLE</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="enable_shader_feedback">
- <property name="toolTip">
- <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
- </property>
- <property name="text">
- <string>Enable Shader Feedback</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="disable_loop_safety_checks">
- <property name="toolTip">
- <string>When checked, it executes shaders without loop logic changes</string>
- </property>
- <property name="text">
- <string>Disable Loop safety checks</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_5">
- <property name="title">
- <string>Debugging</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="2" column="0">
- <widget class="QCheckBox" name="reporting_services">
- <property name="text">
- <string>Enable Verbose Reporting Services**</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QCheckBox" name="fs_access_log">
- <property name="text">
- <string>Enable FS Access Log</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="dump_audio_commands">
- <property name="toolTip">
- <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
- </property>
- <property name="text">
- <string>Dump Audio Commands To Console**</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="create_crash_dumps">
- <property name="text">
- <string>Create Minidump After Crash</string>
- </property>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Logging</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_1">
+ <item row="1" column="1">
+ <widget class="QPushButton" name="open_log_button">
+ <property name="text">
+ <string>Open Log Location</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QWidget" name="logging_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Global Log Filter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="log_filter_edit"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="extended_logging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
+ </property>
+ <property name="text">
+ <string>Enable Extended Logging**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="toggle_console">
+ <property name="text">
+ <string>Show Log in Console</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_6">
- <property name="title">
- <string>Advanced</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="0" column="0">
- <widget class="QCheckBox" name="quest_flag">
- <property name="text">
- <string>Kiosk (Quest) Mode</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="enable_cpu_debugging">
- <property name="text">
- <string>Enable CPU Debugging</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="use_debug_asserts">
- <property name="text">
- <string>Enable Debug Asserts</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QCheckBox" name="use_auto_stub">
- <property name="text">
- <string>Enable Auto-Stub**</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="enable_all_controllers">
- <property name="text">
- <string>Enable All Controller Types</string>
- </property>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Homebrew</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Arguments String</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="homebrew_args_edit"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Graphics</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="disable_loop_safety_checks">
+ <property name="toolTip">
+ <string>When checked, it executes shaders without loop logic changes</string>
+ </property>
+ <property name="text">
+ <string>Disable Loop safety checks</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QCheckBox" name="disable_macro_hle">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
+ </property>
+ <property name="text">
+ <string>Disable Macro HLE</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QCheckBox" name="dump_macros">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it will dump all the macro programs of the GPU</string>
+ </property>
+ <property name="text">
+ <string>Dump Maxwell Macros</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="enable_nsight_aftermath">
+ <property name="toolTip">
+ <string>When checked, it enables Nsight Aftermath crash dumps</string>
+ </property>
+ <property name="text">
+ <string>Enable Nsight Aftermath</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="enable_shader_feedback">
+ <property name="toolTip">
+ <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
+ </property>
+ <property name="text">
+ <string>Enable Shader Feedback</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="disable_macro_jit">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
+ </property>
+ <property name="text">
+ <string>Disable Macro JIT</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="enable_graphics_debugging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the graphics API enters a slower debugging mode</string>
+ </property>
+ <property name="text">
+ <string>Enable Graphics Debugging</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="dump_shaders">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
+ </property>
+ <property name="text">
+ <string>Dump Game Shaders</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="enable_renderdoc_hotkey">
+ <property name="text">
+ <string>Enable Renderdoc Hotkey</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="disable_web_applet">
- <property name="text">
- <string>Disable Web Applet</string>
- </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_6">
+ <property name="title">
+ <string>Advanced</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="perform_vulkan_check">
+ <property name="toolTip">
+ <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
+ </property>
+ <property name="text">
+ <string>Perform Startup Vulkan Check</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="disable_web_applet">
+ <property name="text">
+ <string>Disable Web Applet</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="enable_all_controllers">
+ <property name="text">
+ <string>Enable All Controller Types</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="use_auto_stub">
+ <property name="text">
+ <string>Enable Auto-Stub**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="quest_flag">
+ <property name="text">
+ <string>Kiosk (Quest) Mode</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="enable_cpu_debugging">
+ <property name="text">
+ <string>Enable CPU Debugging</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="use_debug_asserts">
+ <property name="text">
+ <string>Enable Debug Asserts</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <spacer name="verticalSpacer_4">
+ <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>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QCheckBox" name="perform_vulkan_check">
- <property name="toolTip">
- <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
- </property>
- <property name="text">
- <string>Perform Startup Vulkan Check</string>
- </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="title">
+ <string>Debugging</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="fs_access_log">
+ <property name="text">
+ <string>Enable FS Access Log</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="create_crash_dumps">
+ <property name="text">
+ <string>Create Minidump After Crash</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="dump_audio_commands">
+ <property name="toolTip">
+ <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
+ </property>
+ <property name="text">
+ <string>Dump Audio Commands To Console**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="reporting_services">
+ <property name="text">
+ <string>Enable Verbose Reporting Services**</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <spacer name="verticalSpacer_3">
+ <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>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</widget>
</item>
</layout>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_5">
- <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>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <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>
</widget>
<tabstops>
<tabstop>log_filter_edit</tabstop>
@@ -366,14 +562,11 @@
<tabstop>enable_graphics_debugging</tabstop>
<tabstop>enable_shader_feedback</tabstop>
<tabstop>enable_nsight_aftermath</tabstop>
- <tabstop>disable_macro_jit</tabstop>
- <tabstop>disable_loop_safety_checks</tabstop>
<tabstop>fs_access_log</tabstop>
<tabstop>reporting_services</tabstop>
<tabstop>quest_flag</tabstop>
<tabstop>enable_cpu_debugging</tabstop>
<tabstop>use_debug_asserts</tabstop>
- <tabstop>use_auto_stub</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index bdf83ebfe..0ad95cc02 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -4,6 +4,7 @@
#include <memory>
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "ui_configure.h"
#include "vk_device_info.h"
@@ -32,23 +33,28 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_, bool enable_web_config)
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
- registry(registry_), system{system_}, audio_tab{std::make_unique<ConfigureAudio>(system_,
- this)},
- cpu_tab{std::make_unique<ConfigureCpu>(system_, this)},
+ registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
+ this, !system_.IsPoweredOn())},
+ audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
+ cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},
debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
- general_tab{std::make_unique<ConfigureGeneral>(system_, this)},
- graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)},
+ general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)},
+ graphics_advanced_tab{
+ std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)},
+ ui_tab{std::make_unique<ConfigureUi>(system_, this)},
graphics_tab{std::make_unique<ConfigureGraphics>(
system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
- this)},
+ [this](Settings::AspectRatio ratio, Settings::ResolutionSetup setup) {
+ ui_tab->UpdateScreenshotInfo(ratio, setup);
+ },
+ nullptr, *builder, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
input_tab{std::make_unique<ConfigureInput>(system_, this)},
network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)},
- system_tab{std::make_unique<ConfigureSystem>(system_, this)},
- ui_tab{std::make_unique<ConfigureUi>(system_, this)}, web_tab{std::make_unique<ConfigureWeb>(
- this)} {
+ system_tab{std::make_unique<ConfigureSystem>(system_, nullptr, *builder, this)},
+ web_tab{std::make_unique<ConfigureWeb>(this)} {
Settings::SetConfiguringGlobal(true);
ui->setupUi(this);
@@ -95,6 +101,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
adjustSize();
ui->selectorList->setCurrentRow(0);
+
+ // Selects the leftmost button on the bottom bar (Cancel as of writing)
+ ui->buttonBox->setFocus();
}
ConfigureDialog::~ConfigureDialog() = default;
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 2a08b7fee..b28ce288c 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -6,6 +6,9 @@
#include <memory>
#include <vector>
#include <QDialog>
+#include "configuration/shared_widget.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/shared_translation.h"
#include "yuzu/vk_device_info.h"
namespace Core {
@@ -69,6 +72,8 @@ private:
HotkeyRegistry& registry;
Core::System& system;
+ std::unique_ptr<ConfigurationShared::Builder> builder;
+ std::vector<ConfigurationShared::Tab*> tab_group;
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureCpu> cpu_tab;
@@ -76,12 +81,12 @@ private:
std::unique_ptr<ConfigureFilesystem> filesystem_tab;
std::unique_ptr<ConfigureGeneral> general_tab;
std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
+ std::unique_ptr<ConfigureUi> ui_tab;
std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureHotkeys> hotkeys_tab;
std::unique_ptr<ConfigureInput> input_tab;
std::unique_ptr<ConfigureNetwork> network_tab;
std::unique_ptr<ConfigureProfileManager> profile_tab;
std::unique_ptr<ConfigureSystem> system_tab;
- std::unique_ptr<ConfigureUi> ui_tab;
std::unique_ptr<ConfigureWeb> web_tab;
};
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 2f55159f5..c727fadd1 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -3,57 +3,60 @@
#include <functional>
#include <utility>
+#include <vector>
#include <QMessageBox>
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_general.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_general.h"
+#include "yuzu/configuration/shared_widget.h"
#include "yuzu/uisettings.h"
-ConfigureGeneral::ConfigureGeneral(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGeneral>()}, system{system_} {
+ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGeneral>()}, system{system_} {
ui->setupUi(this);
- SetupPerGameUI();
+ Setup(builder);
SetConfiguration();
- if (Settings::IsConfiguringGlobal()) {
- connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit,
- [this]() { ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked()); });
- }
-
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
&ConfigureGeneral::ResetDefaults);
+
+ if (!Settings::IsConfiguringGlobal()) {
+ ui->button_reset_defaults->setVisible(false);
+ }
}
ConfigureGeneral::~ConfigureGeneral() = default;
-void ConfigureGeneral::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
+void ConfigureGeneral::SetConfiguration() {}
+
+void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
+ QLayout& layout = *ui->general_widget->layout();
- ui->use_multi_core->setEnabled(runtime_lock);
- ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
+ std::map<u32, QWidget*> hold{};
- ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
- ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue());
- ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background.GetValue());
- ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue());
- ui->toggle_controller_applet_disabled->setEnabled(runtime_lock);
- ui->toggle_controller_applet_disabled->setChecked(
- UISettings::values.controller_applet_disabled.GetValue());
+ for (const auto setting :
+ UISettings::values.linkage.by_category[Settings::Category::UiGeneral]) {
+ auto* widget = builder.BuildWidget(setting, apply_funcs);
- ui->toggle_speed_limit->setChecked(Settings::values.use_speed_limit.GetValue());
- ui->speed_limit->setValue(Settings::values.speed_limit.GetValue());
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
- ui->button_reset_defaults->setEnabled(runtime_lock);
+ hold.emplace(setting->Id(), widget);
+ }
- if (Settings::IsConfiguringGlobal()) {
- ui->speed_limit->setEnabled(Settings::values.use_speed_limit.GetValue());
- } else {
- ui->speed_limit->setEnabled(Settings::values.use_speed_limit.GetValue() &&
- use_speed_limit != ConfigurationShared::CheckState::Global);
+ for (const auto& [id, widget] : hold) {
+ layout.addWidget(widget);
}
}
@@ -77,32 +80,9 @@ void ConfigureGeneral::ResetDefaults() {
}
void ConfigureGeneral::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core,
- use_multi_core);
-
- if (Settings::IsConfiguringGlobal()) {
- UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
- UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
- UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
- UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
- UISettings::values.controller_applet_disabled =
- ui->toggle_controller_applet_disabled->isChecked();
-
- // Guard if during game and set to game-specific value
- if (Settings::values.use_speed_limit.UsingGlobal()) {
- Settings::values.use_speed_limit.SetValue(ui->toggle_speed_limit->checkState() ==
- Qt::Checked);
- Settings::values.speed_limit.SetValue(ui->speed_limit->value());
- }
- } else {
- bool global_speed_limit = use_speed_limit == ConfigurationShared::CheckState::Global;
- Settings::values.use_speed_limit.SetGlobal(global_speed_limit);
- Settings::values.speed_limit.SetGlobal(global_speed_limit);
- if (!global_speed_limit) {
- Settings::values.use_speed_limit.SetValue(ui->toggle_speed_limit->checkState() ==
- Qt::Checked);
- Settings::values.speed_limit.SetValue(ui->speed_limit->value());
- }
+ bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
}
}
@@ -117,33 +97,3 @@ void ConfigureGeneral::changeEvent(QEvent* event) {
void ConfigureGeneral::RetranslateUI() {
ui->retranslateUi(this);
}
-
-void ConfigureGeneral::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- // Disables each setting if:
- // - A game is running (thus settings in use), and
- // - A non-global setting is applied.
- ui->toggle_speed_limit->setEnabled(Settings::values.use_speed_limit.UsingGlobal());
- ui->speed_limit->setEnabled(Settings::values.speed_limit.UsingGlobal());
-
- return;
- }
-
- ui->toggle_check_exit->setVisible(false);
- ui->toggle_user_on_boot->setVisible(false);
- ui->toggle_background_pause->setVisible(false);
- ui->toggle_hide_mouse->setVisible(false);
- ui->toggle_controller_applet_disabled->setVisible(false);
-
- ui->button_reset_defaults->setVisible(false);
-
- ConfigurationShared::SetColoredTristate(ui->toggle_speed_limit,
- Settings::values.use_speed_limit, use_speed_limit);
- ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
- use_multi_core);
-
- connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() {
- ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() &&
- (use_speed_limit != ConfigurationShared::CheckState::Global));
- });
-}
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 7ff63f425..2d953f679 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -5,48 +5,49 @@
#include <functional>
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
namespace Core {
class System;
}
class ConfigureDialog;
-
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
class HotkeyRegistry;
namespace Ui {
class ConfigureGeneral;
}
-class ConfigureGeneral : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureGeneral : public ConfigurationShared::Tab {
public:
- explicit ConfigureGeneral(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureGeneral(const Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder,
+ QWidget* parent = nullptr);
~ConfigureGeneral() override;
void SetResetCallback(std::function<void()> callback);
void ResetDefaults();
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
+ void Setup(const ConfigurationShared::Builder& builder);
+
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void SetupPerGameUI();
-
std::function<void()> reset_callback;
std::unique_ptr<Ui::ConfigureGeneral> ui;
- ConfigurationShared::CheckState use_speed_limit;
- ConfigurationShared::CheckState use_multi_core;
+ std::vector<std::function<void(bool)>> apply_funcs{};
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index fe757d011..a10e7d3a5 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -26,77 +26,22 @@
</property>
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
- <layout class="QVBoxLayout" name="GeneralVerticalLayout">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QCheckBox" name="toggle_speed_limit">
- <property name="text">
- <string>Limit Speed Percent</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="speed_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_multi_core">
- <property name="text">
- <string>Multicore CPU Emulation</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_check_exit">
- <property name="text">
- <string>Confirm exit while emulation is running</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_user_on_boot">
- <property name="text">
- <string>Prompt for user on game boot</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_background_pause">
- <property name="text">
- <string>Pause emulation when in background</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_hide_mouse">
- <property name="text">
- <string>Hide mouse on inactivity</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="toggle_controller_applet_disabled">
- <property name="text">
- <string>Disable controller applet</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="general_widget" native="true">
+ <layout class="QVBoxLayout" name="GeneralVerticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index a4965524a..fd6bebf0f 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -7,6 +7,7 @@
#include <iterator>
#include <string>
#include <tuple>
+#include <typeinfo>
#include <utility>
#include <vector>
#include <QBoxLayout>
@@ -15,23 +16,30 @@
#include <QComboBox>
#include <QIcon>
#include <QLabel>
+#include <QLineEdit>
#include <QPixmap>
#include <QPushButton>
#include <QSlider>
#include <QStringLiteral>
#include <QtCore/qobjectdefs.h>
+#include <qabstractbutton.h>
+#include <qboxlayout.h>
+#include <qcombobox.h>
#include <qcoreevent.h>
#include <qglobal.h>
+#include <qgridlayout.h>
#include <vulkan/vulkan_core.h>
#include "common/common_types.h"
#include "common/dynamic_library.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "ui_configure_graphics.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
+#include "yuzu/configuration/shared_widget.h"
#include "yuzu/qt_common.h"
#include "yuzu/uisettings.h"
#include "yuzu/vk_device_info.h"
@@ -46,9 +54,9 @@ static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case Settings::VSyncMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
- case Settings::VSyncMode::FIFO:
+ case Settings::VSyncMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
- case Settings::VSyncMode::FIFORelaxed:
+ case Settings::VSyncMode::FifoRelaxed:
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
default:
return VK_PRESENT_MODE_FIFO_KHR;
@@ -62,50 +70,70 @@ static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode)
case VK_PRESENT_MODE_MAILBOX_KHR:
return Settings::VSyncMode::Mailbox;
case VK_PRESENT_MODE_FIFO_KHR:
- return Settings::VSyncMode::FIFO;
+ return Settings::VSyncMode::Fifo;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
- return Settings::VSyncMode::FIFORelaxed;
+ return Settings::VSyncMode::FifoRelaxed;
default:
- return Settings::VSyncMode::FIFO;
+ return Settings::VSyncMode::Fifo;
}
}
-ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
- std::vector<VkDeviceInfo::Record>& records_,
- const std::function<void()>& expose_compute_option_,
- QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, records{records_},
- expose_compute_option{expose_compute_option_}, system{system_} {
+ConfigureGraphics::ConfigureGraphics(
+ const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
+ const std::function<void()>& expose_compute_option_,
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
+ update_aspect_ratio_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()},
+ records{records_}, expose_compute_option{expose_compute_option_},
+ update_aspect_ratio{update_aspect_ratio_}, system{system_},
+ combobox_translations{builder.ComboboxTranslations()},
+ shader_mapping{
+ combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} {
vulkan_device = Settings::values.vulkan_device.GetValue();
RetrieveVulkanDevices();
ui->setupUi(this);
+ Setup(builder);
+
for (const auto& device : vulkan_devices) {
- ui->device->addItem(device);
+ vulkan_device_combobox->addItem(device);
}
- ui->backend->addItem(QStringLiteral("GLSL"));
- ui->backend->addItem(tr("GLASM (Assembly Shaders, NVIDIA Only)"));
- ui->backend->addItem(tr("SPIR-V (Experimental, Mesa Only)"));
-
- SetupPerGameUI();
+ UpdateBackgroundColorButton(QColor::fromRgb(Settings::values.bg_red.GetValue(),
+ Settings::values.bg_green.GetValue(),
+ Settings::values.bg_blue.GetValue()));
+ UpdateAPILayout();
+ PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout
- SetConfiguration();
+ // VSync setting needs to be determined after populating the VSync combobox
+ if (Settings::IsConfiguringGlobal()) {
+ const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue();
+ const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting);
+ int index{};
+ for (const auto mode : vsync_mode_combobox_enum_map) {
+ if (mode == vsync_mode) {
+ break;
+ }
+ index++;
+ }
+ if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) {
+ vsync_mode_combobox->setCurrentIndex(index);
+ }
+ }
- connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
+ connect(api_combobox, qOverload<int>(&QComboBox::activated), this, [this] {
UpdateAPILayout();
PopulateVSyncModeSelection();
- if (!Settings::IsConfiguringGlobal()) {
- ConfigurationShared::SetHighlight(
- ui->api_widget, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
- }
});
- connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) {
- UpdateDeviceSelection(device);
- PopulateVSyncModeSelection();
- });
- connect(ui->backend, qOverload<int>(&QComboBox::activated), this,
+ connect(vulkan_device_combobox, qOverload<int>(&QComboBox::activated), this,
+ [this](int device) {
+ UpdateDeviceSelection(device);
+ PopulateVSyncModeSelection();
+ });
+ connect(shader_backend_combobox, qOverload<int>(&QComboBox::activated), this,
[this](int backend) { UpdateShaderBackendSelection(backend); });
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
@@ -116,39 +144,61 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
UpdateBackgroundColorButton(new_bg_color);
});
- ui->api->setEnabled(!UISettings::values.has_broken_vulkan && ui->api->isEnabled());
+ const auto& update_screenshot_info = [this, &builder]() {
+ const auto& combobox_enumerations = builder.ComboboxTranslations().at(
+ Settings::EnumMetadata<Settings::AspectRatio>::Index());
+ const auto index = aspect_ratio_combobox->currentIndex();
+ const auto ratio = static_cast<Settings::AspectRatio>(combobox_enumerations[index].first);
+
+ const auto& combobox_enumerations_resolution = builder.ComboboxTranslations().at(
+ Settings::EnumMetadata<Settings::ResolutionSetup>::Index());
+ const auto res_index = resolution_combobox->currentIndex();
+ const auto setup = static_cast<Settings::ResolutionSetup>(
+ combobox_enumerations_resolution[res_index].first);
+
+ update_aspect_ratio(ratio, setup);
+ };
+
+ connect(aspect_ratio_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ update_screenshot_info);
+ connect(resolution_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ update_screenshot_info);
+
+ api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled());
ui->api_widget->setEnabled(
(!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) &&
ui->api_widget->isEnabled());
- ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
- ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
- connect(ui->fsr_sharpening_slider, &QSlider::valueChanged, this,
- &ConfigureGraphics::SetFSRIndicatorText);
- ui->fsr_sharpening_combobox->setVisible(!Settings::IsConfiguringGlobal());
- ui->fsr_sharpening_label->setVisible(Settings::IsConfiguringGlobal());
+ if (Settings::IsConfiguringGlobal()) {
+ ui->bg_widget->setEnabled(Settings::values.bg_red.UsingGlobal());
+ }
}
void ConfigureGraphics::PopulateVSyncModeSelection() {
+ if (!Settings::IsConfiguringGlobal()) {
+ return;
+ }
+
const Settings::RendererBackend backend{GetCurrentGraphicsBackend()};
if (backend == Settings::RendererBackend::Null) {
- ui->vsync_mode_combobox->setEnabled(false);
+ vsync_mode_combobox->setEnabled(false);
return;
}
- ui->vsync_mode_combobox->setEnabled(true);
+ vsync_mode_combobox->setEnabled(true);
const int current_index = //< current selected vsync mode from combobox
- ui->vsync_mode_combobox->currentIndex();
+ vsync_mode_combobox->currentIndex();
const auto current_mode = //< current selected vsync mode as a VkPresentModeKHR
current_index == -1 ? VSyncSettingToMode(Settings::values.vsync_mode.GetValue())
: vsync_mode_combobox_enum_map[current_index];
int index{};
- const int device{ui->device->currentIndex()}; //< current selected Vulkan device
+ const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device
+
const auto& present_modes = //< relevant vector of present modes for the selected device or API
- backend == Settings::RendererBackend::Vulkan ? device_present_modes[device]
- : default_present_modes;
+ backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device]
+ : default_present_modes;
- ui->vsync_mode_combobox->clear();
+ vsync_mode_combobox->clear();
vsync_mode_combobox_enum_map.clear();
vsync_mode_combobox_enum_map.reserve(present_modes.size());
for (const auto present_mode : present_modes) {
@@ -157,10 +207,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() {
continue;
}
- ui->vsync_mode_combobox->insertItem(index, mode_name);
+ vsync_mode_combobox->insertItem(index, mode_name);
vsync_mode_combobox_enum_map.push_back(present_mode);
if (present_mode == current_mode) {
- ui->vsync_mode_combobox->setCurrentIndex(index);
+ vsync_mode_combobox->setCurrentIndex(index);
}
index++;
}
@@ -186,112 +236,132 @@ void ConfigureGraphics::UpdateShaderBackendSelection(int backend) {
ConfigureGraphics::~ConfigureGraphics() = default;
-void ConfigureGraphics::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
-
- ui->api_widget->setEnabled(runtime_lock);
- ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
- ui->use_disk_shader_cache->setEnabled(runtime_lock);
- ui->nvdec_emulation_widget->setEnabled(runtime_lock);
- ui->resolution_combobox->setEnabled(runtime_lock);
- ui->accelerate_astc->setEnabled(runtime_lock);
- ui->vsync_mode_layout->setEnabled(runtime_lock ||
- Settings::values.renderer_backend.GetValue() ==
- Settings::RendererBackend::Vulkan);
- ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
- ui->use_asynchronous_gpu_emulation->setChecked(
- Settings::values.use_asynchronous_gpu_emulation.GetValue());
- ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue());
+void ConfigureGraphics::SetConfiguration() {}
+
+void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
+ QLayout* api_layout = ui->api_widget->layout();
+ QWidget* api_grid_widget = new QWidget(this);
+ QVBoxLayout* api_grid_layout = new QVBoxLayout(api_grid_widget);
+ api_grid_layout->setContentsMargins(0, 0, 0, 0);
+ api_layout->addWidget(api_grid_widget);
+
+ QLayout& graphics_layout = *ui->graphics_widget->layout();
+
+ std::map<u32, QWidget*> hold_graphics;
+ std::vector<QWidget*> hold_api;
+
+ for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) {
+ ConfigurationShared::Widget* widget = [&]() {
+ if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) {
+ // FSR needs a reversed slider and a 0.5 multiplier
+ return builder.BuildWidget(
+ setting, apply_funcs, ConfigurationShared::RequestType::ReverseSlider, true,
+ 0.5f, nullptr, tr("%", "FSR sharpening percentage (e.g. 50%)"));
+ } else {
+ return builder.BuildWidget(setting, apply_funcs);
+ }
+ }();
- if (Settings::IsConfiguringGlobal()) {
- ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
- ui->fullscreen_mode_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.fullscreen_mode.GetValue()));
- ui->nvdec_emulation->setCurrentIndex(
- static_cast<int>(Settings::values.nvdec_emulation.GetValue()));
- ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
- ui->resolution_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.resolution_setup.GetValue()));
- ui->scaling_filter_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.scaling_filter.GetValue()));
- ui->fsr_sharpening_slider->setValue(Settings::values.fsr_sharpening_slider.GetValue());
- ui->anti_aliasing_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.anti_aliasing.GetValue()));
- } else {
- ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
- ConfigurationShared::SetHighlight(ui->api_widget,
- !Settings::values.renderer_backend.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->nvdec_emulation,
- &Settings::values.nvdec_emulation);
- ConfigurationShared::SetHighlight(ui->nvdec_emulation_widget,
- !Settings::values.nvdec_emulation.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->fullscreen_mode_combobox,
- &Settings::values.fullscreen_mode);
- ConfigurationShared::SetHighlight(ui->fullscreen_mode_label,
- !Settings::values.fullscreen_mode.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
- &Settings::values.aspect_ratio);
- ConfigurationShared::SetHighlight(ui->ar_label,
- !Settings::values.aspect_ratio.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->resolution_combobox,
- &Settings::values.resolution_setup);
- ConfigurationShared::SetHighlight(ui->resolution_label,
- !Settings::values.resolution_setup.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->scaling_filter_combobox,
- &Settings::values.scaling_filter);
- ConfigurationShared::SetHighlight(ui->scaling_filter_label,
- !Settings::values.scaling_filter.UsingGlobal());
-
- ConfigurationShared::SetPerGameSetting(ui->anti_aliasing_combobox,
- &Settings::values.anti_aliasing);
- ConfigurationShared::SetHighlight(ui->anti_aliasing_label,
- !Settings::values.anti_aliasing.UsingGlobal());
-
- ui->fsr_sharpening_combobox->setCurrentIndex(
- Settings::values.fsr_sharpening_slider.UsingGlobal() ? 0 : 1);
- ui->fsr_sharpening_slider->setEnabled(
- !Settings::values.fsr_sharpening_slider.UsingGlobal());
- ui->fsr_sharpening_value->setEnabled(!Settings::values.fsr_sharpening_slider.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->fsr_sharpening_layout,
- !Settings::values.fsr_sharpening_slider.UsingGlobal());
- ui->fsr_sharpening_slider->setValue(Settings::values.fsr_sharpening_slider.GetValue());
-
- ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
- ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
- }
- UpdateBackgroundColorButton(QColor::fromRgb(Settings::values.bg_red.GetValue(),
- Settings::values.bg_green.GetValue(),
- Settings::values.bg_blue.GetValue()));
- UpdateAPILayout();
- PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout
- SetFSRIndicatorText(ui->fsr_sharpening_slider->sliderPosition());
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
- // VSync setting needs to be determined after populating the VSync combobox
- if (Settings::IsConfiguringGlobal()) {
- const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue();
- const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting);
- int index{};
- for (const auto mode : vsync_mode_combobox_enum_map) {
- if (mode == vsync_mode) {
- break;
+ if (setting->Id() == Settings::values.renderer_backend.Id()) {
+ // Add the renderer combobox now so it's at the top
+ api_grid_layout->addWidget(widget);
+ api_combobox = widget->combobox;
+ api_restore_global_button = widget->restore_button;
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(api_restore_global_button, &QAbstractButton::clicked,
+ [this](bool) { UpdateAPILayout(); });
+
+ // Detach API's restore button and place it where we want
+ // Lets us put it on the side, and it will automatically scale if there's a
+ // second combobox (shader_backend, vulkan_device)
+ widget->layout()->removeWidget(api_restore_global_button);
+ api_layout->addWidget(api_restore_global_button);
}
- index++;
- }
- if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) {
- ui->vsync_mode_combobox->setCurrentIndex(index);
+ } else if (setting->Id() == Settings::values.vulkan_device.Id()) {
+ // Keep track of vulkan_device's combobox so we can populate it
+ hold_api.push_back(widget);
+ vulkan_device_combobox = widget->combobox;
+ vulkan_device_widget = widget;
+ } else if (setting->Id() == Settings::values.shader_backend.Id()) {
+ // Keep track of shader_backend's combobox so we can populate it
+ hold_api.push_back(widget);
+ shader_backend_combobox = widget->combobox;
+ shader_backend_widget = widget;
+ } else if (setting->Id() == Settings::values.vsync_mode.Id()) {
+ // Keep track of vsync_mode's combobox so we can populate it
+ vsync_mode_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->Id() == Settings::values.aspect_ratio.Id()) {
+ // Keep track of the aspect ratio combobox to update other UI tabs that need it
+ aspect_ratio_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->Id() == Settings::values.resolution_setup.Id()) {
+ // Keep track of the resolution combobox to update other UI tabs that need it
+ resolution_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else {
+ hold_graphics.emplace(setting->Id(), widget);
}
}
-}
-void ConfigureGraphics::SetFSRIndicatorText(int percentage) {
- ui->fsr_sharpening_value->setText(
- tr("%1%", "FSR sharpening percentage (e.g. 50%)").arg(100 - (percentage / 2)));
+ for (const auto& [id, widget] : hold_graphics) {
+ graphics_layout.addWidget(widget);
+ }
+
+ for (auto widget : hold_api) {
+ api_grid_layout->addWidget(widget);
+ }
+
+ // Background color is too specific to build into the new system, so we manage it here
+ // (3 settings, all collected into a single widget with a QColor to manage on top)
+ if (Settings::IsConfiguringGlobal()) {
+ apply_funcs.push_back([this](bool powered_on) {
+ Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
+ Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
+ Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
+ });
+ } else {
+ QPushButton* bg_restore_button = ConfigurationShared::Widget::CreateRestoreGlobalButton(
+ Settings::values.bg_red.UsingGlobal(), ui->bg_widget);
+ ui->bg_widget->layout()->addWidget(bg_restore_button);
+
+ QObject::connect(bg_restore_button, &QAbstractButton::clicked,
+ [bg_restore_button, this](bool) {
+ const int r = Settings::values.bg_red.GetValue(true);
+ const int g = Settings::values.bg_green.GetValue(true);
+ const int b = Settings::values.bg_blue.GetValue(true);
+ UpdateBackgroundColorButton(QColor::fromRgb(r, g, b));
+
+ bg_restore_button->setVisible(false);
+ bg_restore_button->setEnabled(false);
+ });
+
+ QObject::connect(ui->bg_button, &QAbstractButton::clicked, [bg_restore_button](bool) {
+ bg_restore_button->setVisible(true);
+ bg_restore_button->setEnabled(true);
+ });
+
+ apply_funcs.push_back([bg_restore_button, this](bool powered_on) {
+ const bool using_global = !bg_restore_button->isEnabled();
+ Settings::values.bg_red.SetGlobal(using_global);
+ Settings::values.bg_green.SetGlobal(using_global);
+ Settings::values.bg_blue.SetGlobal(using_global);
+ if (!using_global) {
+ Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
+ Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
+ Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
+ }
+ });
+ }
}
const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
@@ -315,130 +385,48 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
}
}
+int ConfigureGraphics::FindIndex(u32 enumeration, int value) const {
+ for (u32 i = 0; i < combobox_translations.at(enumeration).size(); i++) {
+ if (combobox_translations.at(enumeration)[i].first == static_cast<u32>(value)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
void ConfigureGraphics::ApplyConfiguration() {
- const auto resolution_setup = static_cast<Settings::ResolutionSetup>(
- ui->resolution_combobox->currentIndex() -
- ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
-
- const auto scaling_filter = static_cast<Settings::ScalingFilter>(
- ui->scaling_filter_combobox->currentIndex() -
- ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
-
- const auto anti_aliasing = static_cast<Settings::AntiAliasing>(
- ui->anti_aliasing_combobox->currentIndex() -
- ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
-
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode,
- ui->fullscreen_mode_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio,
- ui->aspect_ratio_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
- ui->use_disk_shader_cache, use_disk_shader_cache);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
- ui->use_asynchronous_gpu_emulation,
- use_asynchronous_gpu_emulation);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc,
- accelerate_astc);
+ const bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
+ }
if (Settings::IsConfiguringGlobal()) {
- // Guard if during game and set to game-specific value
- if (Settings::values.renderer_backend.UsingGlobal()) {
- Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
- }
- if (Settings::values.nvdec_emulation.UsingGlobal()) {
- Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation());
- }
- if (Settings::values.shader_backend.UsingGlobal()) {
- Settings::values.shader_backend.SetValue(shader_backend);
- }
- if (Settings::values.vulkan_device.UsingGlobal()) {
- Settings::values.vulkan_device.SetValue(vulkan_device);
- }
- if (Settings::values.bg_red.UsingGlobal()) {
- Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
- Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
- Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
- }
- if (Settings::values.resolution_setup.UsingGlobal()) {
- Settings::values.resolution_setup.SetValue(resolution_setup);
- }
- if (Settings::values.scaling_filter.UsingGlobal()) {
- Settings::values.scaling_filter.SetValue(scaling_filter);
- }
- if (Settings::values.anti_aliasing.UsingGlobal()) {
- Settings::values.anti_aliasing.SetValue(anti_aliasing);
- }
- Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
-
- const auto mode = vsync_mode_combobox_enum_map[ui->vsync_mode_combobox->currentIndex()];
+ const auto mode = vsync_mode_combobox_enum_map[vsync_mode_combobox->currentIndex()];
const auto vsync_mode = PresentModeToSetting(mode);
Settings::values.vsync_mode.SetValue(vsync_mode);
- } else {
- if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.resolution_setup.SetGlobal(true);
- } else {
- Settings::values.resolution_setup.SetGlobal(false);
- Settings::values.resolution_setup.SetValue(resolution_setup);
- }
- if (ui->scaling_filter_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.scaling_filter.SetGlobal(true);
- } else {
- Settings::values.scaling_filter.SetGlobal(false);
- Settings::values.scaling_filter.SetValue(scaling_filter);
- }
- if (ui->anti_aliasing_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.anti_aliasing.SetGlobal(true);
- } else {
- Settings::values.anti_aliasing.SetGlobal(false);
- Settings::values.anti_aliasing.SetValue(anti_aliasing);
- }
- if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.renderer_backend.SetGlobal(true);
- Settings::values.shader_backend.SetGlobal(true);
- Settings::values.vulkan_device.SetGlobal(true);
- } else {
- Settings::values.renderer_backend.SetGlobal(false);
- Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
- switch (GetCurrentGraphicsBackend()) {
- case Settings::RendererBackend::OpenGL:
- case Settings::RendererBackend::Null:
- Settings::values.shader_backend.SetGlobal(false);
- Settings::values.vulkan_device.SetGlobal(true);
- Settings::values.shader_backend.SetValue(shader_backend);
- break;
- case Settings::RendererBackend::Vulkan:
- Settings::values.shader_backend.SetGlobal(true);
- Settings::values.vulkan_device.SetGlobal(false);
- Settings::values.vulkan_device.SetValue(vulkan_device);
- break;
- }
- }
-
- if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.nvdec_emulation.SetGlobal(true);
- } else {
- Settings::values.nvdec_emulation.SetGlobal(false);
- Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation());
- }
-
- if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.bg_red.SetGlobal(true);
- Settings::values.bg_green.SetGlobal(true);
- Settings::values.bg_blue.SetGlobal(true);
- } else {
- Settings::values.bg_red.SetGlobal(false);
- Settings::values.bg_green.SetGlobal(false);
- Settings::values.bg_blue.SetGlobal(false);
- Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
- Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
- Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
- }
+ }
- if (ui->fsr_sharpening_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.fsr_sharpening_slider.SetGlobal(true);
- } else {
- Settings::values.fsr_sharpening_slider.SetGlobal(false);
- Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
+ Settings::values.vulkan_device.SetGlobal(true);
+ Settings::values.shader_backend.SetGlobal(true);
+ if (Settings::IsConfiguringGlobal() ||
+ (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) {
+ auto backend = static_cast<Settings::RendererBackend>(
+ combobox_translations
+ .at(Settings::EnumMetadata<
+ Settings::RendererBackend>::Index())[api_combobox->currentIndex()]
+ .first);
+ switch (backend) {
+ case Settings::RendererBackend::OpenGL:
+ Settings::values.shader_backend.SetGlobal(Settings::IsConfiguringGlobal());
+ Settings::values.shader_backend.SetValue(static_cast<Settings::ShaderBackend>(
+ shader_mapping[shader_backend_combobox->currentIndex()].first));
+ break;
+ case Settings::RendererBackend::Vulkan:
+ Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal());
+ Settings::values.vulkan_device.SetValue(vulkan_device_combobox->currentIndex());
+ break;
+ case Settings::RendererBackend::Null:
+ break;
}
}
}
@@ -466,36 +454,26 @@ void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
}
void ConfigureGraphics::UpdateAPILayout() {
- if (!Settings::IsConfiguringGlobal() &&
- ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- vulkan_device = Settings::values.vulkan_device.GetValue(true);
- shader_backend = Settings::values.shader_backend.GetValue(true);
- ui->device_widget->setEnabled(false);
- ui->backend_widget->setEnabled(false);
- } else {
- vulkan_device = Settings::values.vulkan_device.GetValue();
- shader_backend = Settings::values.shader_backend.GetValue();
- ui->device_widget->setEnabled(true);
- ui->backend_widget->setEnabled(true);
- }
-
- switch (GetCurrentGraphicsBackend()) {
- case Settings::RendererBackend::OpenGL:
- ui->backend->setCurrentIndex(static_cast<u32>(shader_backend));
- ui->device_widget->setVisible(false);
- ui->backend_widget->setVisible(true);
- break;
- case Settings::RendererBackend::Vulkan:
- if (static_cast<int>(vulkan_device) < ui->device->count()) {
- ui->device->setCurrentIndex(vulkan_device);
- }
- ui->device_widget->setVisible(true);
- ui->backend_widget->setVisible(false);
- break;
- case Settings::RendererBackend::Null:
- ui->device_widget->setVisible(false);
- ui->backend_widget->setVisible(false);
- break;
+ bool runtime_lock = !system.IsPoweredOn();
+ bool need_global = !(Settings::IsConfiguringGlobal() || api_restore_global_button->isEnabled());
+ vulkan_device = Settings::values.vulkan_device.GetValue(need_global);
+ shader_backend = Settings::values.shader_backend.GetValue(need_global);
+ vulkan_device_widget->setEnabled(!need_global && runtime_lock);
+ shader_backend_widget->setEnabled(!need_global && runtime_lock);
+
+ const auto current_backend = GetCurrentGraphicsBackend();
+ const bool is_opengl = current_backend == Settings::RendererBackend::OpenGL;
+ const bool is_vulkan = current_backend == Settings::RendererBackend::Vulkan;
+
+ vulkan_device_widget->setVisible(is_vulkan);
+ shader_backend_widget->setVisible(is_opengl);
+
+ if (is_opengl) {
+ shader_backend_combobox->setCurrentIndex(
+ FindIndex(Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
+ static_cast<int>(shader_backend)));
+ } else if (is_vulkan && static_cast<int>(vulkan_device) < vulkan_device_combobox->count()) {
+ vulkan_device_combobox->setCurrentIndex(vulkan_device);
}
}
@@ -515,92 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
- if (Settings::IsConfiguringGlobal()) {
- return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
- }
-
- if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.renderer_backend.SetGlobal(true);
- return Settings::values.renderer_backend.GetValue();
- }
- Settings::values.renderer_backend.SetGlobal(false);
- return static_cast<Settings::RendererBackend>(ui->api->currentIndex() -
- ConfigurationShared::USE_GLOBAL_OFFSET);
-}
-
-Settings::NvdecEmulation ConfigureGraphics::GetCurrentNvdecEmulation() const {
- if (Settings::IsConfiguringGlobal()) {
- return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex());
- }
-
- if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
- Settings::values.nvdec_emulation.SetGlobal(true);
- return Settings::values.nvdec_emulation.GetValue();
- }
- Settings::values.nvdec_emulation.SetGlobal(false);
- return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex() -
- ConfigurationShared::USE_GLOBAL_OFFSET);
-}
-
-void ConfigureGraphics::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
- ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal());
- ui->fullscreen_mode_combobox->setEnabled(Settings::values.fullscreen_mode.UsingGlobal());
- ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
- ui->resolution_combobox->setEnabled(Settings::values.resolution_setup.UsingGlobal());
- ui->scaling_filter_combobox->setEnabled(Settings::values.scaling_filter.UsingGlobal());
- ui->fsr_sharpening_slider->setEnabled(Settings::values.fsr_sharpening_slider.UsingGlobal());
- ui->anti_aliasing_combobox->setEnabled(Settings::values.anti_aliasing.UsingGlobal());
- ui->use_asynchronous_gpu_emulation->setEnabled(
- Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
- ui->nvdec_emulation->setEnabled(Settings::values.nvdec_emulation.UsingGlobal());
- ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal());
- ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
- ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
- ui->fsr_slider_layout->setEnabled(Settings::values.fsr_sharpening_slider.UsingGlobal());
-
- return;
+ const auto selected_backend = [&]() {
+ if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
+ return Settings::values.renderer_backend.GetValue(true);
+ }
+ return static_cast<Settings::RendererBackend>(
+ combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
+ .at(api_combobox->currentIndex())
+ .first);
+ }();
+
+ if (selected_backend == Settings::RendererBackend::Vulkan &&
+ UISettings::values.has_broken_vulkan) {
+ return Settings::RendererBackend::OpenGL;
}
-
- connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
- ui->bg_button->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
- });
-
- connect(ui->fsr_sharpening_combobox, qOverload<int>(&QComboBox::activated), this,
- [this](int index) {
- ui->fsr_sharpening_slider->setEnabled(index == 1);
- ui->fsr_sharpening_value->setEnabled(index == 1);
- ConfigurationShared::SetHighlight(ui->fsr_sharpening_layout, index == 1);
- });
-
- ConfigurationShared::SetColoredTristate(
- ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
- ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc,
- accelerate_astc);
- ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
- Settings::values.use_asynchronous_gpu_emulation,
- use_asynchronous_gpu_emulation);
-
- ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label,
- Settings::values.aspect_ratio.GetValue(true));
- ConfigurationShared::SetColoredComboBox(
- ui->fullscreen_mode_combobox, ui->fullscreen_mode_label,
- static_cast<int>(Settings::values.fullscreen_mode.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->resolution_combobox, ui->resolution_label,
- static_cast<int>(Settings::values.resolution_setup.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->scaling_filter_combobox, ui->scaling_filter_label,
- static_cast<int>(Settings::values.scaling_filter.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->anti_aliasing_combobox, ui->anti_aliasing_label,
- static_cast<int>(Settings::values.anti_aliasing.GetValue(true)));
- ConfigurationShared::InsertGlobalItem(
- ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
- ConfigurationShared::InsertGlobalItem(
- ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
-
- ui->vsync_mode_layout->setVisible(false);
+ return selected_backend;
}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index be9310b74..9c24a56db 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -5,6 +5,8 @@
#include <functional>
#include <memory>
+#include <type_traits>
+#include <typeindex>
#include <vector>
#include <QColor>
#include <QString>
@@ -12,10 +14,15 @@
#include <qobjectdefs.h>
#include <vulkan/vulkan_core.h>
#include "common/common_types.h"
+#include "common/settings_enums.h"
+#include "configuration/shared_translation.h"
#include "vk_device_info.h"
+#include "yuzu/configuration/configuration_shared.h"
+class QPushButton;
class QEvent;
class QObject;
+class QComboBox;
namespace Settings {
enum class NvdecEmulation : u32;
@@ -27,31 +34,34 @@ namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureGraphics;
}
-class ConfigureGraphics : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureGraphics : public ConfigurationShared::Tab {
public:
- explicit ConfigureGraphics(const Core::System& system_,
- std::vector<VkDeviceInfo::Record>& records,
- const std::function<void()>& expose_compute_option_,
- QWidget* parent = nullptr);
+ explicit ConfigureGraphics(
+ const Core::System& system_, std::vector<VkDeviceInfo::Record>& records,
+ const std::function<void()>& expose_compute_option,
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
+ update_aspect_ratio,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureGraphics() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
+ void Setup(const ConfigurationShared::Builder& builder);
+
void PopulateVSyncModeSelection();
void UpdateBackgroundColorButton(QColor color);
void UpdateAPILayout();
@@ -60,34 +70,43 @@ private:
void RetrieveVulkanDevices();
- void SetFSRIndicatorText(int percentage);
/* Turns a Vulkan present mode into a textual string for a UI
* (and eventually for a human to read) */
const QString TranslateVSyncMode(VkPresentModeKHR mode,
Settings::RendererBackend backend) const;
- void SetupPerGameUI();
-
Settings::RendererBackend GetCurrentGraphicsBackend() const;
- Settings::NvdecEmulation GetCurrentNvdecEmulation() const;
+
+ int FindIndex(u32 enumeration, int value) const;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
- ConfigurationShared::CheckState use_nvdec_emulation;
- ConfigurationShared::CheckState accelerate_astc;
- ConfigurationShared::CheckState use_disk_shader_cache;
- ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
+ std::vector<std::function<void(bool)>> apply_funcs{};
std::vector<VkDeviceInfo::Record>& records;
std::vector<QString> vulkan_devices;
std::vector<std::vector<VkPresentModeKHR>> device_present_modes;
std::vector<VkPresentModeKHR>
- vsync_mode_combobox_enum_map; //< Keeps track of which present mode corresponds to which
- // selection in the combobox
+ vsync_mode_combobox_enum_map{}; //< Keeps track of which present mode corresponds to which
+ // selection in the combobox
u32 vulkan_device{};
Settings::ShaderBackend shader_backend{};
const std::function<void()>& expose_compute_option;
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)> update_aspect_ratio;
const Core::System& system;
+ const ConfigurationShared::ComboboxTranslationMap& combobox_translations;
+ const std::vector<std::pair<u32, QString>>& shader_mapping;
+
+ QPushButton* api_restore_global_button;
+ QComboBox* vulkan_device_combobox;
+ QComboBox* api_combobox;
+ QComboBox* shader_backend_combobox;
+ QComboBox* vsync_mode_combobox;
+ QWidget* vulkan_device_widget;
+ QWidget* api_widget;
+ QWidget* shader_backend_widget;
+ QComboBox* aspect_ratio_combobox;
+ QComboBox* resolution_combobox;
};
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 39f70e406..d09415d70 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -27,7 +27,7 @@
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="api_widget" native="true">
- <layout class="QGridLayout" name="gridLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -40,115 +40,6 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <property name="horizontalSpacing">
- <number>6</number>
- </property>
- <item row="4" column="0">
- <widget class="QWidget" name="backend_widget" native="true">
- <layout class="QHBoxLayout" name="backend_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="backend_label">
- <property name="text">
- <string>Shader Backend:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="backend"/>
- </item>
- </layout>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QWidget" name="device_widget" native="true">
- <layout class="QHBoxLayout" name="device_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="device_label">
- <property name="text">
- <string>Device:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="device"/>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QWidget" name="api_layout_2" native="true">
- <layout class="QHBoxLayout" name="api_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="api_label">
- <property name="text">
- <string>API:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="api">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string notr="true">OpenGL</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">Vulkan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
</layout>
</widget>
</item>
@@ -168,111 +59,8 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="QCheckBox" name="use_disk_shader_cache">
- <property name="text">
- <string>Use disk pipeline cache</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
- <property name="text">
- <string>Use asynchronous GPU emulation</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="accelerate_astc">
- <property name="text">
- <string>Accelerate ASTC texture decoding</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="vsync_mode_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="vsync_mode_label">
- <property name="text">
- <string>VSync Mode:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="vsync_mode_combobox">
- <property name="toolTip">
- <string>FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
-FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
-Mailbox can have lower latency than FIFO and does not tear but may drop frames.
-Immediate (no synchronization) just presents whatever is available and can exhibit tearing.</string>
- </property>
- <property name="currentText">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="nvdec_emulation_widget" native="true">
- <layout class="QHBoxLayout" name="nvdec_emulation_layout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="nvdec_emulation_label">
- <property name="text">
- <string>NVDEC emulation:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="nvdec_emulation">
- <item>
- <property name="text">
- <string>No Video Output</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>CPU Video Decoding</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GPU Video Decoding (Default)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="fullscreen_mode_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <widget class="QWidget" name="graphics_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -285,33 +73,12 @@ Immediate (no synchronization) just presents whatever is available and can exhib
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QLabel" name="fullscreen_mode_label">
- <property name="text">
- <string>Fullscreen Mode:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="fullscreen_mode_combobox">
- <item>
- <property name="text">
- <string>Borderless Windowed</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Exclusive Fullscreen</string>
- </property>
- </item>
- </widget>
- </item>
</layout>
</widget>
</item>
<item>
- <widget class="QWidget" name="aspect_ratio_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <widget class="QWidget" name="bg_widget" native="true">
+ <layout class="QHBoxLayout" name="bg_layout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -325,452 +92,35 @@ Immediate (no synchronization) just presents whatever is available and can exhib
<number>0</number>
</property>
<item>
- <widget class="QLabel" name="ar_label">
- <property name="text">
- <string>Aspect Ratio:</string>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="aspect_ratio_combobox">
- <item>
- <property name="text">
- <string>Default (16:9)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Force 4:3</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Force 21:9</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Force 16:10</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Stretch to Window</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="resolution_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="resolution_label">
- <property name="text">
- <string>Resolution:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="resolution_combobox">
- <item>
- <property name="text">
- <string>0.5X (360p/540p) [EXPERIMENTAL]</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>0.75X (540p/810p) [EXPERIMENTAL]</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>1X (720p/1080p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>2X (1440p/2160p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>3X (2160p/3240p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>4X (2880p/4320p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>5X (3600p/5400p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>6X (4320p/6480p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>7X (5040p/7560p)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>8X (5760p/8640p)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="scaling_filter_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="scaling_filter_label">
- <property name="text">
- <string>Window Adapting Filter:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="scaling_filter_combobox">
- <item>
- <property name="text">
- <string>Nearest Neighbor</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Bilinear</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Bicubic</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Gaussian</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>ScaleForce</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>AMD FidelityFX™️ Super Resolution</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="anti_aliasing_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="anti_aliasing_label">
- <property name="text">
- <string>Anti-Aliasing Method:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anti_aliasing_combobox">
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>FXAA</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>SMAA</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="fsr_sharpening_layout" native="true">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="fsr_sharpening_label_group">
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QComboBox" name="fsr_sharpening_combobox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
- <property name="text">
- <string>Use global FSR Sharpness</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set FSR Sharpness</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="fsr_sharpening_label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>FSR Sharpness:</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="fsr_slider_layout">
- <property name="spacing">
- <number>6</number>
- </property>
- <item>
- <widget class="QSlider" name="fsr_sharpening_slider">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="baseSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximum">
- <number>200</number>
- </property>
- <property name="sliderPosition">
- <number>25</number>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="invertedAppearance">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="fsr_sharpening_value">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>100%</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="bg_layout" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QComboBox" name="bg_combobox">
- <property name="currentText">
- <string>Use global background color</string>
- </property>
- <property name="currentIndex">
- <number>0</number>
- </property>
- <property name="maxVisibleItems">
- <number>10</number>
- </property>
- <item>
- <property name="text">
- <string>Use global background color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Set background color:</string>
- </property>
- </item>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="bg_label">
<property name="text">
<string>Background Color:</string>
</property>
</widget>
</item>
<item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
<widget class="QPushButton" name="bg_button">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
+ <property name="text">
+ <string/>
+ </property>
</widget>
</item>
</layout>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index c0a044767..4db18673d 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -1,104 +1,68 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <vector>
+#include <QLabel>
+#include <qnamespace.h>
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_graphics_advanced.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics_advanced.h"
+#include "yuzu/configuration/shared_translation.h"
+#include "yuzu/configuration/shared_widget.h"
-ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphicsAdvanced>()}, system{system_} {
+ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(
+ const Core::System& system_, std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphicsAdvanced>()}, system{system_} {
ui->setupUi(this);
- SetupPerGameUI();
+ Setup(builder);
SetConfiguration();
- ui->enable_compute_pipelines_checkbox->setVisible(false);
+ checkbox_enable_compute_pipelines->setVisible(false);
}
ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;
-void ConfigureGraphicsAdvanced::SetConfiguration() {
- const bool runtime_lock = !system.IsPoweredOn();
- ui->use_reactive_flushing->setEnabled(runtime_lock);
- ui->async_present->setEnabled(runtime_lock);
- ui->renderer_force_max_clock->setEnabled(runtime_lock);
- ui->async_astc->setEnabled(runtime_lock);
- ui->astc_recompression_combobox->setEnabled(runtime_lock);
- ui->use_asynchronous_shaders->setEnabled(runtime_lock);
- ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
- ui->enable_compute_pipelines_checkbox->setEnabled(runtime_lock);
-
- ui->async_present->setChecked(Settings::values.async_presentation.GetValue());
- ui->renderer_force_max_clock->setChecked(Settings::values.renderer_force_max_clock.GetValue());
- ui->use_reactive_flushing->setChecked(Settings::values.use_reactive_flushing.GetValue());
- ui->async_astc->setChecked(Settings::values.async_astc.GetValue());
- ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
- ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
- ui->use_vulkan_driver_pipeline_cache->setChecked(
- Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
- ui->enable_compute_pipelines_checkbox->setChecked(
- Settings::values.enable_compute_pipelines.GetValue());
- ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
- ui->barrier_feedback_loops_checkbox->setChecked(
- Settings::values.barrier_feedback_loops.GetValue());
-
- if (Settings::IsConfiguringGlobal()) {
- ui->gpu_accuracy->setCurrentIndex(
- static_cast<int>(Settings::values.gpu_accuracy.GetValue()));
- ui->anisotropic_filtering_combobox->setCurrentIndex(
- Settings::values.max_anisotropy.GetValue());
- ui->astc_recompression_combobox->setCurrentIndex(
- static_cast<int>(Settings::values.astc_recompression.GetValue()));
- } else {
- ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
- ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
- &Settings::values.max_anisotropy);
- ConfigurationShared::SetPerGameSetting(ui->astc_recompression_combobox,
- &Settings::values.astc_recompression);
- ConfigurationShared::SetHighlight(ui->label_gpu_accuracy,
- !Settings::values.gpu_accuracy.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->af_label,
- !Settings::values.max_anisotropy.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_astc_recompression,
- !Settings::values.astc_recompression.UsingGlobal());
+void ConfigureGraphicsAdvanced::SetConfiguration() {}
+
+void ConfigureGraphicsAdvanced::Setup(const ConfigurationShared::Builder& builder) {
+ auto& layout = *ui->populate_target->layout();
+ std::map<u32, QWidget*> hold{}; // A map will sort the data for us
+
+ for (auto setting :
+ Settings::values.linkage.by_category[Settings::Category::RendererAdvanced]) {
+ ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
+
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ hold.emplace(setting->Id(), widget);
+
+ // Keep track of enable_compute_pipelines so we can display it when needed
+ if (setting->Id() == Settings::values.enable_compute_pipelines.Id()) {
+ checkbox_enable_compute_pipelines = widget;
+ }
+ }
+ for (const auto& [id, widget] : hold) {
+ layout.addWidget(widget);
}
}
void ConfigureGraphicsAdvanced::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation,
- ui->async_present, async_present);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.renderer_force_max_clock,
- ui->renderer_force_max_clock,
- renderer_force_max_clock);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
- ui->anisotropic_filtering_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_reactive_flushing,
- ui->use_reactive_flushing, use_reactive_flushing);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc,
- async_astc);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.astc_recompression,
- ui->astc_recompression_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,
- ui->use_asynchronous_shaders,
- use_asynchronous_shaders);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
- ui->use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vulkan_driver_pipeline_cache,
- ui->use_vulkan_driver_pipeline_cache,
- use_vulkan_driver_pipeline_cache);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
- ui->enable_compute_pipelines_checkbox,
- enable_compute_pipelines);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
- ui->use_video_framerate_checkbox, use_video_framerate);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.barrier_feedback_loops,
- ui->barrier_feedback_loops_checkbox,
- barrier_feedback_loops);
+ const bool is_powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(is_powered_on);
+ }
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -113,71 +77,6 @@ void ConfigureGraphicsAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
-void ConfigureGraphicsAdvanced::SetupPerGameUI() {
- // Disable if not global (only happens during game)
- if (Settings::IsConfiguringGlobal()) {
- ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
- ui->async_present->setEnabled(Settings::values.async_presentation.UsingGlobal());
- ui->renderer_force_max_clock->setEnabled(
- Settings::values.renderer_force_max_clock.UsingGlobal());
- ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal());
- ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal());
- ui->astc_recompression_combobox->setEnabled(
- Settings::values.astc_recompression.UsingGlobal());
- ui->use_asynchronous_shaders->setEnabled(
- Settings::values.use_asynchronous_shaders.UsingGlobal());
- ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
- ui->use_vulkan_driver_pipeline_cache->setEnabled(
- Settings::values.use_vulkan_driver_pipeline_cache.UsingGlobal());
- ui->anisotropic_filtering_combobox->setEnabled(
- Settings::values.max_anisotropy.UsingGlobal());
- ui->enable_compute_pipelines_checkbox->setEnabled(
- Settings::values.enable_compute_pipelines.UsingGlobal());
- ui->use_video_framerate_checkbox->setEnabled(
- Settings::values.use_video_framerate.UsingGlobal());
- ui->barrier_feedback_loops_checkbox->setEnabled(
- Settings::values.barrier_feedback_loops.UsingGlobal());
-
- return;
- }
-
- ConfigurationShared::SetColoredTristate(ui->async_present, Settings::values.async_presentation,
- async_present);
- ConfigurationShared::SetColoredTristate(ui->renderer_force_max_clock,
- Settings::values.renderer_force_max_clock,
- renderer_force_max_clock);
- ConfigurationShared::SetColoredTristate(
- ui->use_reactive_flushing, Settings::values.use_reactive_flushing, use_reactive_flushing);
- ConfigurationShared::SetColoredTristate(ui->async_astc, Settings::values.async_astc,
- async_astc);
- ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
- Settings::values.use_asynchronous_shaders,
- use_asynchronous_shaders);
- ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
- Settings::values.use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::SetColoredTristate(ui->use_vulkan_driver_pipeline_cache,
- Settings::values.use_vulkan_driver_pipeline_cache,
- use_vulkan_driver_pipeline_cache);
- ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
- Settings::values.enable_compute_pipelines,
- enable_compute_pipelines);
- ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
- Settings::values.use_video_framerate,
- use_video_framerate);
- ConfigurationShared::SetColoredTristate(ui->barrier_feedback_loops_checkbox,
- Settings::values.barrier_feedback_loops,
- barrier_feedback_loops);
- ConfigurationShared::SetColoredComboBox(
- ui->gpu_accuracy, ui->label_gpu_accuracy,
- static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->anisotropic_filtering_combobox, ui->af_label,
- static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
- ConfigurationShared::SetColoredComboBox(
- ui->astc_recompression_combobox, ui->label_astc_recompression,
- static_cast<int>(Settings::values.astc_recompression.GetValue(true)));
-}
-
void ConfigureGraphicsAdvanced::ExposeComputeOption() {
- ui->enable_compute_pipelines_checkbox->setVisible(true);
+ checkbox_enable_compute_pipelines->setVisible(true);
}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 369a7c83e..78b5389c3 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -4,51 +4,44 @@
#pragma once
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureGraphicsAdvanced;
}
-class ConfigureGraphicsAdvanced : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureGraphicsAdvanced : public ConfigurationShared::Tab {
public:
- explicit ConfigureGraphicsAdvanced(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureGraphicsAdvanced(
+ const Core::System& system_, std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder, QWidget* parent = nullptr);
~ConfigureGraphicsAdvanced() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
void ExposeComputeOption();
private:
+ void Setup(const ConfigurationShared::Builder& builder);
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void SetupPerGameUI();
-
std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
- ConfigurationShared::CheckState async_present;
- ConfigurationShared::CheckState renderer_force_max_clock;
- ConfigurationShared::CheckState use_vsync;
- ConfigurationShared::CheckState async_astc;
- ConfigurationShared::CheckState use_reactive_flushing;
- ConfigurationShared::CheckState use_asynchronous_shaders;
- ConfigurationShared::CheckState use_fast_gpu_time;
- ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
- ConfigurationShared::CheckState enable_compute_pipelines;
- ConfigurationShared::CheckState use_video_framerate;
- ConfigurationShared::CheckState barrier_feedback_loops;
-
const Core::System& system;
+
+ std::vector<std::function<void(bool)>> apply_funcs;
+
+ QWidget* checkbox_enable_compute_pipelines{};
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index d527a6f38..37a854ca3 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -26,8 +26,8 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <widget class="QWidget" name="gpu_accuracy_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <widget class="QWidget" name="populate_target" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -40,233 +40,6 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item>
- <widget class="QLabel" name="label_gpu_accuracy">
- <property name="text">
- <string>Accuracy Level:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="gpu_accuracy">
- <item>
- <property name="text">
- <string notr="true">Normal</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">High</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">Extreme(very slow)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="astc_recompression_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="label_astc_recompression">
- <property name="text">
- <string>ASTC recompression:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="astc_recompression_combobox">
- <item>
- <property name="text">
- <string>Uncompressed (Best quality)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>BC1 (Low quality)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>BC3 (Medium quality)</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="async_present">
- <property name="text">
- <string>Enable asynchronous presentation (Vulkan only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="renderer_force_max_clock">
- <property name="toolTip">
- <string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string>
- </property>
- <property name="text">
- <string>Force maximum clocks (Vulkan only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="async_astc">
- <property name="toolTip">
- <string>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</string>
- </property>
- <property name="text">
- <string>Decode ASTC textures asynchronously (Hack)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_reactive_flushing">
- <property name="toolTip">
- <string>Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.</string>
- </property>
- <property name="text">
- <string>Enable Reactive Flushing</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_asynchronous_shaders">
- <property name="toolTip">
- <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
- </property>
- <property name="text">
- <string>Use asynchronous shader building (Hack)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_fast_gpu_time">
- <property name="toolTip">
- <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>
- </property>
- <property name="text">
- <string>Use Fast GPU Time (Hack)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache">
- <property name="toolTip">
- <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string>
- </property>
- <property name="text">
- <string>Use Vulkan pipeline cache</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="enable_compute_pipelines_checkbox">
- <property name="toolTip">
- <string>Enable compute pipelines, required by some games. This setting only exists for Intel proprietary drivers, and may crash if enabled.
-Compute pipelines are always enabled on all other drivers.</string>
- </property>
- <property name="text">
- <string>Enable Compute Pipelines (Intel Vulkan only)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="use_video_framerate_checkbox">
- <property name="toolTip">
- <string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
- </property>
- <property name="text">
- <string>Sync to framerate of video playback</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="barrier_feedback_loops_checkbox">
- <property name="toolTip">
- <string>Improves rendering of transparency effects in specific games.</string>
- </property>
- <property name="text">
- <string>Barrier feedback loops</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="af_layout" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="af_label">
- <property name="text">
- <string>Anisotropic Filtering:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="anisotropic_filtering_combobox">
- <item>
- <property name="text">
- <string>Automatic</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>2x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>4x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>8x</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>16x</string>
- </property>
- </item>
- </widget>
- </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7fce85bca..e8f9ebfd8 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -4,6 +4,8 @@
#include <memory>
#include <thread>
+#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -197,9 +199,11 @@ void ConfigureInput::ApplyConfiguration() {
advanced->ApplyConfiguration();
- const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
- Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue(), system);
+ const bool pre_docked_mode = Settings::IsDockedMode();
+ const bool docked_mode_selected = ui->radioDocked->isChecked();
+ Settings::values.use_docked_mode.SetValue(
+ docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
+ OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
@@ -267,8 +271,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+ ui->radioDocked->setChecked(Settings::IsDockedMode());
+ ui->radioUndocked->setChecked(!Settings::IsDockedMode());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index eb96e6068..b91d6ad4a 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -17,6 +17,9 @@
#include <QTimer>
#include "common/fs/fs_util.h"
+#include "common/settings_enums.h"
+#include "common/settings_input.h"
+#include "configuration/shared_widget.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -24,9 +27,9 @@
#include "core/loader/loader.h"
#include "ui_configure_per_game.h"
#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
#include "yuzu/configuration/configure_cpu.h"
-#include "yuzu/configuration/configure_general.h"
#include "yuzu/configuration/configure_graphics.h"
#include "yuzu/configuration/configure_graphics_advanced.h"
#include "yuzu/configuration/configure_input_per_game.h"
@@ -41,26 +44,28 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_)
: QDialog(parent),
- ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_} {
+ ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
+ builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
+ tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
- audio_tab = std::make_unique<ConfigureAudio>(system_, this);
- cpu_tab = std::make_unique<ConfigureCpu>(system_, this);
- general_tab = std::make_unique<ConfigureGeneral>(system_, this);
- graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this);
+ audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
+ cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this);
+ graphics_advanced_tab =
+ std::make_unique<ConfigureGraphicsAdvanced>(system_, tab_group, *builder, this);
graphics_tab = std::make_unique<ConfigureGraphics>(
- system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this);
+ system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
+ [](Settings::AspectRatio, Settings::ResolutionSetup) {}, tab_group, *builder, this);
input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
- system_tab = std::make_unique<ConfigureSystem>(system_, this);
+ system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this);
ui->setupUi(this);
ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons"));
- ui->tabWidget->addTab(general_tab.get(), tr("General"));
ui->tabWidget->addTab(system_tab.get(), tr("System"));
ui->tabWidget->addTab(cpu_tab.get(), tr("CPU"));
ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics"));
@@ -88,15 +93,18 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
ConfigurePerGame::~ConfigurePerGame() = default;
void ConfigurePerGame::ApplyConfiguration() {
+ for (const auto tab : *tab_group) {
+ tab->ApplyConfiguration();
+ }
addons_tab->ApplyConfiguration();
- general_tab->ApplyConfiguration();
- cpu_tab->ApplyConfiguration();
- system_tab->ApplyConfiguration();
- graphics_tab->ApplyConfiguration();
- graphics_advanced_tab->ApplyConfiguration();
- audio_tab->ApplyConfiguration();
input_tab->ApplyConfiguration();
+ if (Settings::IsDockedMode() && Settings::values.players.GetValue()[0].controller_type ==
+ Settings::ControllerType::Handheld) {
+ Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
+ Settings::values.use_docked_mode.SetGlobal(true);
+ }
+
system.ApplySettings();
Settings::LogSettings();
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 7ec1ded06..1a727f32c 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -10,9 +10,12 @@
#include <QDialog>
#include <QList>
+#include "configuration/shared_widget.h"
#include "core/file_sys/vfs_types.h"
#include "vk_device_info.h"
#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/shared_translation.h"
namespace Core {
class System;
@@ -25,7 +28,6 @@ class InputSubsystem;
class ConfigurePerGameAddons;
class ConfigureAudio;
class ConfigureCpu;
-class ConfigureGeneral;
class ConfigureGraphics;
class ConfigureGraphicsAdvanced;
class ConfigureInputPerGame;
@@ -73,11 +75,12 @@ private:
std::unique_ptr<Config> game_config;
Core::System& system;
+ std::unique_ptr<ConfigurationShared::Builder> builder;
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> tab_group;
std::unique_ptr<ConfigurePerGameAddons> addons_tab;
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureCpu> cpu_tab;
- std::unique_ptr<ConfigureGeneral> general_tab;
std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureInputPerGame> input_tab;
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui
index 85c86e107..99ba2fd18 100644
--- a/src/yuzu/configuration/configure_per_game.ui
+++ b/src/yuzu/configuration/configure_per_game.ui
@@ -2,6 +2,14 @@
<ui version="4.0">
<class>ConfigurePerGame</class>
<widget class="QDialog" name="ConfigurePerGame">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>900</width>
+ <height>607</height>
+ </rect>
+ </property>
<property name="minimumSize">
<size>
<width>900</width>
@@ -225,20 +233,31 @@
</layout>
</item>
<item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Some settings are only available when a game is not running.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index f1ae312c6..0c8e5c8b4 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -3,16 +3,23 @@
#include <chrono>
#include <optional>
+#include <vector>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDateTimeEdit>
#include <QFileDialog>
#include <QGraphicsItem>
+#include <QLineEdit>
#include <QMessageBox>
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/time/time_manager.h"
#include "ui_configure_system.h"
+#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_system.h"
+#include "yuzu/configuration/shared_widget.h"
constexpr std::array<u32, 7> LOCALE_BLOCKLIST{
// pzzefezrpnkzeidfej
@@ -37,44 +44,32 @@ static bool IsValidLocale(u32 region_index, u32 language_index) {
return ((LOCALE_BLOCKLIST.at(region_index) >> language_index) & 1) == 0;
}
-ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} {
+ConfigureSystem::ConfigureSystem(Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} {
ui->setupUi(this);
- connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
- ui->rng_seed_edit->setEnabled(state == Qt::Checked);
- if (state != Qt::Checked) {
- ui->rng_seed_edit->setText(QStringLiteral("00000000"));
- }
- });
-
- connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
- ui->custom_rtc_edit->setEnabled(state == Qt::Checked);
- if (state != Qt::Checked) {
- ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
- }
- });
+ Setup(builder);
- const auto locale_check = [this](int index) {
- const auto region_index = ConfigurationShared::GetComboboxIndex(
- Settings::values.region_index.GetValue(true), ui->combo_region);
- const auto language_index = ConfigurationShared::GetComboboxIndex(
- Settings::values.language_index.GetValue(true), ui->combo_language);
+ const auto locale_check = [this]() {
+ const auto region_index = combo_region->currentIndex();
+ const auto language_index = combo_language->currentIndex();
const bool valid_locale = IsValidLocale(region_index, language_index);
ui->label_warn_invalid_locale->setVisible(!valid_locale);
if (!valid_locale) {
ui->label_warn_invalid_locale->setText(
tr("Warning: \"%1\" is not a valid language for region \"%2\"")
- .arg(ui->combo_language->currentText())
- .arg(ui->combo_region->currentText()));
+ .arg(combo_language->currentText())
+ .arg(combo_region->currentText()));
}
};
- connect(ui->combo_language, qOverload<int>(&QComboBox::currentIndexChanged), this,
- locale_check);
- connect(ui->combo_region, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
+ connect(combo_language, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
+ connect(combo_region, qOverload<int>(&QComboBox::currentIndexChanged), this, locale_check);
- SetupPerGameUI();
+ ui->label_warn_invalid_locale->setVisible(false);
+ locale_check();
SetConfiguration();
}
@@ -93,137 +88,71 @@ void ConfigureSystem::RetranslateUI() {
ui->retranslateUi(this);
}
-void ConfigureSystem::SetConfiguration() {
- enabled = !system.IsPoweredOn();
- const auto rng_seed =
- QStringLiteral("%1")
- .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'})
- .toUpper();
- const auto rtc_time = Settings::values.custom_rtc.value_or(QDateTime::currentSecsSinceEpoch());
-
- ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value());
- ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() &&
- Settings::values.rng_seed.UsingGlobal());
- ui->rng_seed_edit->setText(rng_seed);
-
- ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
- ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
- ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time));
- ui->device_name_edit->setText(
- QString::fromUtf8(Settings::values.device_name.GetValue().c_str()));
- ui->use_unsafe_extended_memory_layout->setEnabled(enabled);
- ui->use_unsafe_extended_memory_layout->setChecked(
- Settings::values.use_unsafe_extended_memory_layout.GetValue());
-
- if (Settings::IsConfiguringGlobal()) {
- ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
- ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue());
- ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue());
- } else {
- ConfigurationShared::SetPerGameSetting(ui->combo_language,
- &Settings::values.language_index);
- ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index);
- ConfigurationShared::SetPerGameSetting(ui->combo_time_zone,
- &Settings::values.time_zone_index);
-
- ConfigurationShared::SetHighlight(ui->label_language,
- !Settings::values.language_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_region,
- !Settings::values.region_index.UsingGlobal());
- ConfigurationShared::SetHighlight(ui->label_timezone,
- !Settings::values.time_zone_index.UsingGlobal());
- }
-}
+void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) {
+ auto& core_layout = *ui->core_widget->layout();
+ auto& system_layout = *ui->system_widget->layout();
-void ConfigureSystem::ReadSystemSettings() {}
+ std::map<u32, QWidget*> core_hold{};
+ std::map<u32, QWidget*> system_hold{};
-void ConfigureSystem::ApplyConfiguration() {
- // Allow setting custom RTC even if system is powered on,
- // to allow in-game time to be fast forwarded
- if (Settings::IsConfiguringGlobal()) {
- if (ui->custom_rtc_checkbox->isChecked()) {
- Settings::values.custom_rtc = ui->custom_rtc_edit->dateTime().toSecsSinceEpoch();
- if (system.IsPoweredOn()) {
- const s64 posix_time{*Settings::values.custom_rtc};
- system.GetTimeManager().UpdateLocalSystemClockTime(posix_time);
- }
- } else {
- Settings::values.custom_rtc = std::nullopt;
+ std::vector<Settings::BasicSetting*> settings;
+ auto push = [&settings](auto& list) {
+ for (auto setting : list) {
+ settings.push_back(setting);
}
- }
+ };
- Settings::values.device_name = ui->device_name_edit->text().toStdString();
+ push(Settings::values.linkage.by_category[Settings::Category::Core]);
+ push(Settings::values.linkage.by_category[Settings::Category::System]);
- if (!enabled) {
- return;
- }
+ for (auto setting : settings) {
+ if (setting->Id() == Settings::values.use_docked_mode.Id() &&
+ Settings::IsConfiguringGlobal()) {
+ continue;
+ }
+
+ ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, ui->combo_language);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,
- ui->combo_time_zone);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_unsafe_extended_memory_layout,
- ui->use_unsafe_extended_memory_layout,
- use_unsafe_extended_memory_layout);
-
- if (Settings::IsConfiguringGlobal()) {
- // Guard if during game and set to game-specific value
- if (Settings::values.rng_seed.UsingGlobal()) {
- if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
+ if (widget == nullptr) {
+ continue;
}
- } else {
- switch (use_rng_seed) {
- case ConfigurationShared::CheckState::On:
- case ConfigurationShared::CheckState::Off:
- Settings::values.rng_seed.SetGlobal(false);
- if (ui->rng_seed_checkbox->isChecked()) {
- Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toUInt(nullptr, 16));
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
- break;
- case ConfigurationShared::CheckState::Global:
- Settings::values.rng_seed.SetGlobal(false);
- Settings::values.rng_seed.SetValue(std::nullopt);
- Settings::values.rng_seed.SetGlobal(true);
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ if (setting->Id() == Settings::values.region_index.Id()) {
+ // Keep track of the region_index (and langauge_index) combobox to validate the selected
+ // settings
+ combo_region = widget->combobox;
+ } else if (setting->Id() == Settings::values.language_index.Id()) {
+ combo_language = widget->combobox;
+ }
+
+ switch (setting->GetCategory()) {
+ case Settings::Category::Core:
+ core_hold.emplace(setting->Id(), widget);
break;
- case ConfigurationShared::CheckState::Count:
+ case Settings::Category::System:
+ system_hold.emplace(setting->Id(), widget);
break;
+ default:
+ widget->deleteLater();
}
}
+ for (const auto& [label, widget] : core_hold) {
+ core_layout.addWidget(widget);
+ }
+ for (const auto& [id, widget] : system_hold) {
+ system_layout.addWidget(widget);
+ }
}
-void ConfigureSystem::SetupPerGameUI() {
- if (Settings::IsConfiguringGlobal()) {
- ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal());
- ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal());
- ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal());
- ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal());
- ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal());
+void ConfigureSystem::SetConfiguration() {}
- return;
+void ConfigureSystem::ApplyConfiguration() {
+ const bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
}
-
- ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language,
- Settings::values.language_index.GetValue(true));
- ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region,
- Settings::values.region_index.GetValue(true));
- ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone,
- Settings::values.time_zone_index.GetValue(true));
-
- ConfigurationShared::SetColoredTristate(
- ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(),
- Settings::values.rng_seed.GetValue().has_value(),
- Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
-
- ConfigurationShared::SetColoredTristate(ui->use_unsafe_extended_memory_layout,
- Settings::values.use_unsafe_extended_memory_layout,
- use_unsafe_extended_memory_layout);
-
- ui->custom_rtc_checkbox->setVisible(false);
- ui->custom_rtc_edit->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index ce1a91601..eab99a48a 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -3,45 +3,53 @@
#pragma once
+#include <functional>
#include <memory>
+#include <vector>
#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+class QCheckBox;
+class QLineEdit;
+class QComboBox;
+class QDateTimeEdit;
namespace Core {
class System;
}
-namespace ConfigurationShared {
-enum class CheckState;
-}
-
namespace Ui {
class ConfigureSystem;
}
-class ConfigureSystem : public QWidget {
- Q_OBJECT
+namespace ConfigurationShared {
+class Builder;
+}
+class ConfigureSystem : public ConfigurationShared::Tab {
public:
- explicit ConfigureSystem(Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureSystem(Core::System& system_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+ const ConfigurationShared::Builder& builder,
+ QWidget* parent = nullptr);
~ConfigureSystem() override;
- void ApplyConfiguration();
- void SetConfiguration();
+ void ApplyConfiguration() override;
+ void SetConfiguration() override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
- void ReadSystemSettings();
+ void Setup(const ConfigurationShared::Builder& builder);
- void SetupPerGameUI();
+ std::vector<std::function<void(bool)>> apply_funcs{};
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
- ConfigurationShared::CheckState use_rng_seed;
- ConfigurationShared::CheckState use_unsafe_extended_memory_layout;
-
Core::System& system;
+
+ QComboBox* combo_region;
+ QComboBox* combo_language;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index e0caecd5e..2a735836e 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>366</width>
+ <width>605</width>
<height>483</height>
</rect>
</property>
@@ -22,470 +22,63 @@
<item>
<widget class="QGroupBox" name="group_system_settings">
<property name="title">
- <string>System Settings</string>
+ <string>System</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="0">
- <widget class="QLabel" name="label_region">
- <property name="text">
- <string>Region:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="combo_time_zone">
- <item>
- <property name="text">
- <string>Auto</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>CET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>CST6CDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Cuba</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>EET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Egypt</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Eire</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>EST</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>EST5EDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GB</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GB-Eire</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT+0</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT-0</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>GMT0</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Greenwich</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Hongkong</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>HST</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Iceland</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Iran</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Israel</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Jamaica</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Japan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Kwajalein</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Libya</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>MET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>MST</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>MST7MDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Navajo</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>NZ</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>NZ-CHAT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Poland</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Portugal</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>PRC</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>PST8PDT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>ROC</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>ROK</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Singapore</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Turkey</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>UCT</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Universal</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>UTC</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>W-SU</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>WET</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Zulu</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="combo_region">
- <item>
- <property name="text">
- <string>Japan</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>USA</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Europe</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Australia</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>China</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Korea</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Taiwan</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_timezone">
- <property name="text">
- <string>Time Zone:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="combo_language">
- <property name="toolTip">
- <string>Note: this can be overridden when region setting is auto-select</string>
- </property>
- <item>
- <property name="text">
- <string>Japanese (日本語)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>American English</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>French (français)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>German (Deutsch)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Italian (italiano)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Spanish (español)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Chinese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Korean (한국어)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Dutch (Nederlands)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Portuguese (português)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Russian (Русский)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Taiwanese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>British English</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Canadian French</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Latin American Spanish</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Simplified Chinese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Traditional Chinese (正體中文)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Brazilian Portuguese (português do Brasil)</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="custom_rtc_checkbox">
- <property name="text">
- <string>Custom RTC</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_language">
- <property name="text">
- <string>Language</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QCheckBox" name="rng_seed_checkbox">
- <property name="text">
- <string>RNG Seed</string>
- </property>
- </widget>
- </item>
- <item row="6" column="0">
- <widget class="QLabel" name="device_name_label">
- <property name="text">
- <string>Device Name</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QDateTimeEdit" name="custom_rtc_edit">
- <property name="minimumDate">
- <date>
- <year>1970</year>
- <month>1</month>
- <day>1</day>
- </date>
- </property>
- </widget>
- </item>
- <item row="6" column="1">
- <widget class="QLineEdit" name="device_name_edit">
- <property name="maxLength">
- <number>128</number>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QLineEdit" name="rng_seed_edit">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <family>Lucida Console</family>
- </font>
- </property>
- <property name="inputMask">
- <string notr="true">HHHHHHHH</string>
- </property>
- <property name="maxLength">
- <number>8</number>
- </property>
- </widget>
- </item>
- <item row="7" column="0">
- <widget class="QCheckBox" name="use_unsafe_extended_memory_layout">
- <property name="text">
- <string>Unsafe extended memory layout (8GB DRAM)</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="system_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_warn_invalid_locale">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Core</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QWidget" name="core_widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
@@ -503,26 +96,6 @@
</property>
</spacer>
</item>
- <item>
- <widget class="QLabel" name="label_warn_invalid_locale">
- <property name="text">
- <string></string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_disable_info">
- <property name="text">
- <string>System settings are available only when game is not running.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
</layout>
</item>
</layout>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 2ebb80302..82f3b6e78 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -1,18 +1,32 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "yuzu/configuration/configure_ui.h"
+
#include <array>
+#include <cstdlib>
+#include <set>
+#include <stdexcept>
+#include <string>
#include <utility>
-#include <QFileDialog>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QCoreApplication>
#include <QDirIterator>
+#include <QFileDialog>
+#include <QString>
+#include <QToolButton>
+#include <QVariant>
+
#include "common/common_types.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
#include "core/core.h"
+#include "core/frontend/framebuffer_layout.h"
#include "ui_configure_ui.h"
-#include "yuzu/configuration/configure_ui.h"
#include "yuzu/uisettings.h"
namespace {
@@ -54,8 +68,40 @@ QString GetTranslatedRowTextName(size_t index) {
}
} // Anonymous namespace
+static float GetUpFactor(Settings::ResolutionSetup res_setup) {
+ Settings::ResolutionScalingInfo info{};
+ Settings::TranslateResolutionInfo(res_setup, info);
+ return info.up_factor;
+}
+
+static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* parent) {
+ screenshot_height->clear();
+
+ const auto& enumeration =
+ Settings::EnumMetadata<Settings::ResolutionSetup>::Canonicalizations();
+ std::set<u32> resolutions{};
+ for (const auto& [name, value] : enumeration) {
+ const float up_factor = GetUpFactor(value);
+ u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
+ u32 height_docked = Layout::ScreenDocked::Height * up_factor;
+ resolutions.emplace(height_undocked);
+ resolutions.emplace(height_docked);
+ }
+
+ screenshot_height->addItem(parent->tr("Auto", "Screenshot height option"));
+ for (const auto res : resolutions) {
+ screenshot_height->addItem(QString::fromStdString(std::to_string(res)));
+ }
+}
+
+static u32 ScreenshotDimensionToInt(const QString& height) {
+ return std::strtoul(height.toUtf8(), nullptr, 0);
+}
+
ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()}, system{system_} {
+ : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()},
+ ratio{Settings::values.aspect_ratio.GetValue()},
+ resolution_setting{Settings::values.resolution_setup.GetValue()}, system{system_} {
ui->setupUi(this);
InitializeLanguageComboBox();
@@ -68,6 +114,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
InitializeIconSizeComboBox();
InitializeRowComboBoxes();
+ PopulateResolutionComboBox(ui->screenshot_height, this);
+
SetConfiguration();
// Force game list reload if any of the relevant settings are changed.
@@ -75,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_play_time, &QCheckBox::stateChanged, this,
+ &ConfigureUi::RequestGameListUpdate);
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureUi::RequestGameListUpdate);
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -104,6 +154,10 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
ui->screenshot_path_edit->setText(dir);
}
});
+
+ connect(ui->screenshot_height, &QComboBox::currentTextChanged, [this]() { UpdateWidthText(); });
+
+ UpdateWidthText();
}
ConfigureUi::~ConfigureUi() = default;
@@ -115,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.show_compat = ui->show_compat->isChecked();
UISettings::values.show_size = ui->show_size->isChecked();
UISettings::values.show_types = ui->show_types->isChecked();
+ UISettings::values.show_play_time = ui->show_play_time->isChecked();
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -123,6 +178,11 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir,
ui->screenshot_path_edit->text().toStdString());
+
+ const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
+ UISettings::values.screenshot_height.SetValue(height);
+
+ RequestGameListUpdate();
system.ApplySettings();
}
@@ -138,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
ui->show_types->setChecked(UISettings::values.show_types.GetValue());
+ ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
ui->game_icon_size_combobox->setCurrentIndex(
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
ui->folder_icon_size_combobox->setCurrentIndex(
@@ -147,6 +208,13 @@ void ConfigureUi::SetConfiguration() {
UISettings::values.enable_screenshot_save_as.GetValue());
ui->screenshot_path_edit->setText(QString::fromStdString(
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)));
+
+ const auto height = UISettings::values.screenshot_height.GetValue();
+ if (height == 0) {
+ ui->screenshot_height->setCurrentIndex(0);
+ } else {
+ ui->screenshot_height->setCurrentText(QStringLiteral("%1").arg(height));
+ }
}
void ConfigureUi::changeEvent(QEvent* event) {
@@ -317,3 +385,29 @@ void ConfigureUi::OnLanguageChanged(int index) {
emit LanguageChanged(ui->language_combobox->itemData(index).toString());
}
+
+void ConfigureUi::UpdateWidthText() {
+ const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
+ const u32 width = UISettings::CalculateWidth(height, ratio);
+ if (height == 0) {
+ const auto up_factor = GetUpFactor(resolution_setting);
+ const u32 height_docked = Layout::ScreenDocked::Height * up_factor;
+ const u32 width_docked = UISettings::CalculateWidth(height_docked, ratio);
+ const u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
+ const u32 width_undocked = UISettings::CalculateWidth(height_undocked, ratio);
+ ui->screenshot_width->setText(tr("Auto (%1 x %2, %3 x %4)", "Screenshot width value")
+ .arg(width_undocked)
+ .arg(height_undocked)
+ .arg(width_docked)
+ .arg(height_docked));
+ } else {
+ ui->screenshot_width->setText(QStringLiteral("%1 x").arg(width));
+ }
+}
+
+void ConfigureUi::UpdateScreenshotInfo(Settings::AspectRatio ratio_,
+ Settings::ResolutionSetup resolution_setting_) {
+ ratio = ratio_;
+ resolution_setting = resolution_setting_;
+ UpdateWidthText();
+}
diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h
index 95af8370e..2a2563a13 100644
--- a/src/yuzu/configuration/configure_ui.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -5,6 +5,7 @@
#include <memory>
#include <QWidget>
+#include "common/settings_enums.h"
namespace Core {
class System;
@@ -23,6 +24,9 @@ public:
void ApplyConfiguration();
+ void UpdateScreenshotInfo(Settings::AspectRatio ratio,
+ Settings::ResolutionSetup resolution_info);
+
private slots:
void OnLanguageChanged(int index);
@@ -44,7 +48,11 @@ private:
void UpdateFirstRowComboBox(bool init = false);
void UpdateSecondRowComboBox(bool init = false);
+ void UpdateWidthText();
+
std::unique_ptr<Ui::ConfigureUi> ui;
+ Settings::AspectRatio ratio;
+ Settings::ResolutionSetup resolution_setting;
Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index 10bb27312..b8e648381 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>363</width>
- <height>562</height>
+ <height>603</height>
</rect>
</property>
<property name="windowTitle">
@@ -105,6 +105,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="show_play_time">
+ <property name="text">
+ <string>Show Play Time Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
<item>
<widget class="QLabel" name="game_icon_size_label">
@@ -201,6 +208,41 @@
</item>
</layout>
</item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="screenshot_width">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="screenshot_height">
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Resolution:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
new file mode 100644
index 000000000..a4e8af1b4
--- /dev/null
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -0,0 +1,392 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/time_zone.h"
+#include "yuzu/configuration/shared_translation.h"
+
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <QWidget>
+#include "common/settings.h"
+#include "common/settings_enums.h"
+#include "common/settings_setting.h"
+#include "yuzu/uisettings.h"
+
+namespace ConfigurationShared {
+
+std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
+ std::unique_ptr<TranslationMap> translations = std::make_unique<TranslationMap>();
+ const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
+
+#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
+ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{tr((NAME)), tr((TOOLTIP))}})
+
+ // A setting can be ignored by giving it a blank name
+
+ // Audio
+ INSERT(Settings, sink_id, "Output Engine:", "");
+ INSERT(Settings, audio_output_device_id, "Output Device:", "");
+ INSERT(Settings, audio_input_device_id, "Input Device:", "");
+ INSERT(Settings, audio_muted, "Mute audio", "");
+ INSERT(Settings, volume, "Volume:", "");
+ INSERT(Settings, dump_audio_commands, "", "");
+ INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
+
+ // Core
+ INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
+ INSERT(Settings, memory_layout_mode, "Memory Layout", "");
+ INSERT(Settings, use_speed_limit, "", "");
+ INSERT(Settings, speed_limit, "Limit Speed Percent", "");
+
+ // Cpu
+ INSERT(Settings, cpu_accuracy, "Accuracy:", "");
+
+ // Cpu Debug
+
+ // Cpu Unsafe
+ INSERT(Settings, cpuopt_unsafe_unfuse_fma,
+ "Unfuse FMA (improve performance on CPUs without FMA)",
+ "This option improves speed by reducing accuracy of fused-multiply-add instructions on "
+ "CPUs without native FMA support.");
+ INSERT(Settings, cpuopt_unsafe_reduce_fp_error, "Faster FRSQRTE and FRECPE",
+ "This option improves the speed of some approximate floating-point functions by using "
+ "less accurate native approximations.");
+ INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, "Faster ASIMD instructions (32 bits only)",
+ "This option improves the speed of 32 bits ASIMD floating-point functions by running "
+ "with incorrect rounding modes.");
+ INSERT(Settings, cpuopt_unsafe_inaccurate_nan, "Inaccurate NaN handling",
+ "This option improves speed by removing NaN checking. Please note this also reduces "
+ "accuracy of certain floating-point instructions.");
+ INSERT(
+ Settings, cpuopt_unsafe_fastmem_check, "Disable address space checks",
+ "This option improves speed by eliminating a safety check before every memory read/write "
+ "in guest. Disabling it may allow a game to read/write the emulator's memory.");
+ INSERT(Settings, cpuopt_unsafe_ignore_global_monitor, "Ignore global monitor",
+ "This option improves speed by relying only on the semantics of cmpxchg to ensure "
+ "safety of exclusive access instructions. Please note this may result in deadlocks and "
+ "other race conditions.");
+
+ // Renderer
+ INSERT(Settings, renderer_backend, "API:", "");
+ INSERT(Settings, vulkan_device, "Device:", "");
+ INSERT(Settings, shader_backend, "Shader Backend:", "");
+ INSERT(Settings, resolution_setup, "Resolution:", "");
+ INSERT(Settings, scaling_filter, "Window Adapting Filter:", "");
+ INSERT(Settings, fsr_sharpening_slider, "FSR Sharpness:", "");
+ INSERT(Settings, anti_aliasing, "Anti-Aliasing Method:", "");
+ INSERT(Settings, fullscreen_mode, "Fullscreen Mode:", "");
+ INSERT(Settings, aspect_ratio, "Aspect Ratio:", "");
+ INSERT(Settings, use_disk_shader_cache, "Use disk pipeline cache", "");
+ INSERT(Settings, use_asynchronous_gpu_emulation, "Use asynchronous GPU emulation", "");
+ INSERT(Settings, nvdec_emulation, "NVDEC emulation:", "");
+ INSERT(Settings, accelerate_astc, "ASTC Decoding Method:", "");
+ INSERT(Settings, astc_recompression, "ASTC Recompression Method:", "");
+ INSERT(Settings, vsync_mode, "VSync Mode:",
+ "FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
+ "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from "
+ "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop "
+ "frames.\nImmediate (no synchronization) just presents whatever is available and can "
+ "exhibit tearing.");
+ INSERT(Settings, bg_red, "", "");
+ INSERT(Settings, bg_green, "", "");
+ INSERT(Settings, bg_blue, "", "");
+
+ // Renderer (Advanced Graphics)
+ INSERT(Settings, async_presentation, "Enable asynchronous presentation (Vulkan only)", "");
+ INSERT(Settings, renderer_force_max_clock, "Force maximum clocks (Vulkan only)",
+ "Runs work in the background while waiting for graphics commands to keep the GPU from "
+ "lowering its clock speed.");
+ INSERT(Settings, max_anisotropy, "Anisotropic Filtering:", "");
+ INSERT(Settings, gpu_accuracy, "Accuracy Level:", "");
+ INSERT(Settings, use_asynchronous_shaders, "Use asynchronous shader building (Hack)",
+ "Enables asynchronous shader compilation, which may reduce shader stutter. This feature "
+ "is experimental.");
+ INSERT(Settings, use_fast_gpu_time, "Use Fast GPU Time (Hack)",
+ "Enables Fast GPU Time. This option will force most games to run at their highest "
+ "native resolution.");
+ INSERT(Settings, use_vulkan_driver_pipeline_cache, "Use Vulkan pipeline cache",
+ "Enables GPU vendor-specific pipeline cache. This option can improve shader loading "
+ "time significantly in cases where the Vulkan driver does not store pipeline cache "
+ "files internally.");
+ INSERT(Settings, enable_compute_pipelines, "Enable Compute Pipelines (Intel Vulkan Only)",
+ "Enable compute pipelines, required by some games.\nThis setting only exists for Intel "
+ "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled "
+ "on all other drivers.");
+ INSERT(Settings, use_reactive_flushing, "Enable Reactive Flushing",
+ "Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
+ "syncing.");
+ INSERT(Settings, use_video_framerate, "Sync to framerate of video playback",
+ "Run the game at normal speed during video playback, even when the framerate is "
+ "unlocked.");
+ INSERT(Settings, barrier_feedback_loops, "Barrier feedback loops",
+ "Improves rendering of transparency effects in specific games.");
+
+ // Renderer (Debug)
+
+ // System
+ INSERT(Settings, rng_seed, "RNG Seed", "");
+ INSERT(Settings, rng_seed_enabled, "", "");
+ INSERT(Settings, device_name, "Device Name", "");
+ INSERT(Settings, custom_rtc, "Custom RTC", "");
+ INSERT(Settings, custom_rtc_enabled, "", "");
+ INSERT(Settings, language_index,
+ "Language:", "Note: this can be overridden when region setting is auto-select");
+ INSERT(Settings, region_index, "Region:", "");
+ INSERT(Settings, time_zone_index, "Time Zone:", "");
+ INSERT(Settings, sound_index, "Sound Output Mode:", "");
+ INSERT(Settings, use_docked_mode, "Console Mode:", "");
+ INSERT(Settings, current_user, "", "");
+
+ // Controls
+
+ // Data Storage
+
+ // Debugging
+
+ // Debugging Graphics
+
+ // Network
+
+ // Web Service
+
+ // Ui
+
+ // Ui General
+ INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
+ INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
+ INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
+ INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
+ INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
+
+ // Ui Debugging
+
+ // Ui Multiplayer
+
+ // Ui Games list
+
+#undef INSERT
+
+ return translations;
+}
+
+std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
+ std::unique_ptr<ComboboxTranslationMap> translations =
+ std::make_unique<ComboboxTranslationMap>();
+ const auto& tr = [&](const char* text, const char* context = "") {
+ return parent->tr(text, context);
+ };
+
+#define PAIR(ENUM, VALUE, TRANSLATION) \
+ { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION) }
+#define CTX_PAIR(ENUM, VALUE, TRANSLATION, CONTEXT) \
+ { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION, CONTEXT) }
+
+ // Intentionally skipping VSyncMode to let the UI fill that one out
+
+ translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
+ {
+ PAIR(AstcDecodeMode, Cpu, "CPU"),
+ PAIR(AstcDecodeMode, Gpu, "GPU"),
+ PAIR(AstcDecodeMode, CpuAsynchronous, "CPU Asynchronous"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
+ {
+ PAIR(AstcRecompression, Uncompressed, "Uncompressed (Best quality)"),
+ PAIR(AstcRecompression, Bc1, "BC1 (Low quality)"),
+ PAIR(AstcRecompression, Bc3, "BC3 (Medium quality)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
+ {
+#ifdef HAS_OPENGL
+ PAIR(RendererBackend, OpenGL, "OpenGL"),
+#endif
+ PAIR(RendererBackend, Vulkan, "Vulkan"),
+ PAIR(RendererBackend, Null, "Null"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
+ {
+ PAIR(ShaderBackend, Glsl, "GLSL"),
+ PAIR(ShaderBackend, Glasm, "GLASM (Assembly Shaders, NVIDIA Only)"),
+ PAIR(ShaderBackend, SpirV, "SPIR-V (Experimental, Mesa Only)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
+ {
+ PAIR(GpuAccuracy, Normal, "Normal"),
+ PAIR(GpuAccuracy, High, "High"),
+ PAIR(GpuAccuracy, Extreme, "Extreme"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
+ {
+ PAIR(CpuAccuracy, Auto, "Auto"),
+ PAIR(CpuAccuracy, Accurate, "Accurate"),
+ PAIR(CpuAccuracy, Unsafe, "Unsafe"),
+ PAIR(CpuAccuracy, Paranoid, "Paranoid (disables most optimizations)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(),
+ {
+ PAIR(FullscreenMode, Borderless, "Borderless Windowed"),
+ PAIR(FullscreenMode, Exclusive, "Exclusive Fullscreen"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::NvdecEmulation>::Index(),
+ {
+ PAIR(NvdecEmulation, Off, "No Video Output"),
+ PAIR(NvdecEmulation, Cpu, "CPU Video Decoding"),
+ PAIR(NvdecEmulation, Gpu, "GPU Video Decoding (Default)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
+ {
+ PAIR(ResolutionSetup, Res1_2X, "0.5X (360p/540p) [EXPERIMENTAL]"),
+ PAIR(ResolutionSetup, Res3_4X, "0.75X (540p/810p) [EXPERIMENTAL]"),
+ PAIR(ResolutionSetup, Res1X, "1X (720p/1080p)"),
+ PAIR(ResolutionSetup, Res3_2X, "1.5X (1080p/1620p) [EXPERIMENTAL]"),
+ PAIR(ResolutionSetup, Res2X, "2X (1440p/2160p)"),
+ PAIR(ResolutionSetup, Res3X, "3X (2160p/3240p)"),
+ PAIR(ResolutionSetup, Res4X, "4X (2880p/4320p)"),
+ PAIR(ResolutionSetup, Res5X, "5X (3600p/5400p)"),
+ PAIR(ResolutionSetup, Res6X, "6X (4320p/6480p)"),
+ PAIR(ResolutionSetup, Res7X, "7X (5040p/7560p)"),
+ PAIR(ResolutionSetup, Res8X, "8X (5760p/8640p)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(),
+ {
+ PAIR(ScalingFilter, NearestNeighbor, "Nearest Neighbor"),
+ PAIR(ScalingFilter, Bilinear, "Bilinear"),
+ PAIR(ScalingFilter, Bicubic, "Bicubic"),
+ PAIR(ScalingFilter, Gaussian, "Gaussian"),
+ PAIR(ScalingFilter, ScaleForce, "ScaleForce"),
+ PAIR(ScalingFilter, Fsr, "AMD FidelityFX™️ Super Resolution"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
+ {
+ PAIR(AntiAliasing, None, "None"),
+ PAIR(AntiAliasing, Fxaa, "FXAA"),
+ PAIR(AntiAliasing, Smaa, "SMAA"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AspectRatio>::Index(),
+ {
+ PAIR(AspectRatio, R16_9, "Default (16:9)"),
+ PAIR(AspectRatio, R4_3, "Force 4:3"),
+ PAIR(AspectRatio, R21_9, "Force 21:9"),
+ PAIR(AspectRatio, R16_10, "Force 16:10"),
+ PAIR(AspectRatio, Stretch, "Stretch to Window"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AnisotropyMode>::Index(),
+ {
+ PAIR(AnisotropyMode, Automatic, "Automatic"),
+ PAIR(AnisotropyMode, Default, "Default"),
+ PAIR(AnisotropyMode, X2, "2x"),
+ PAIR(AnisotropyMode, X4, "4x"),
+ PAIR(AnisotropyMode, X8, "8x"),
+ PAIR(AnisotropyMode, X16, "16x"),
+ }});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::Language>::Index(),
+ {
+ PAIR(Language, Japanese, "Japanese (日本語)"),
+ PAIR(Language, EnglishAmerican, "American English"),
+ PAIR(Language, French, "French (français)"),
+ PAIR(Language, German, "German (Deutsch)"),
+ PAIR(Language, Italian, "Italian (italiano)"),
+ PAIR(Language, Spanish, "Spanish (español)"),
+ PAIR(Language, Chinese, "Chinese"),
+ PAIR(Language, Korean, "Korean (한국어)"),
+ PAIR(Language, Dutch, "Dutch (Nederlands)"),
+ PAIR(Language, Portuguese, "Portuguese (português)"),
+ PAIR(Language, Russian, "Russian (Русский)"),
+ PAIR(Language, Taiwanese, "Taiwanese"),
+ PAIR(Language, EnglishBritish, "British English"),
+ PAIR(Language, FrenchCanadian, "Canadian French"),
+ PAIR(Language, SpanishLatin, "Latin American Spanish"),
+ PAIR(Language, ChineseSimplified, "Simplified Chinese"),
+ PAIR(Language, ChineseTraditional, "Traditional Chinese (正體中文)"),
+ PAIR(Language, PortugueseBrazilian, "Brazilian Portuguese (português do Brasil)"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::Region>::Index(),
+ {
+ PAIR(Region, Japan, "Japan"),
+ PAIR(Region, Usa, "USA"),
+ PAIR(Region, Europe, "Europe"),
+ PAIR(Region, Australia, "Australia"),
+ PAIR(Region, China, "China"),
+ PAIR(Region, Korea, "Korea"),
+ PAIR(Region, Taiwan, "Taiwan"),
+ }});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::TimeZone>::Index(),
+ {
+ {static_cast<u32>(Settings::TimeZone::Auto),
+ tr("Auto (%1)", "Auto select time zone")
+ .arg(QString::fromStdString(
+ Settings::GetTimeZoneString(Settings::TimeZone::Auto)))},
+ {static_cast<u32>(Settings::TimeZone::Default),
+ tr("Default (%1)", "Default time zone")
+ .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
+ PAIR(TimeZone, Cet, "CET"),
+ PAIR(TimeZone, Cst6Cdt, "CST6CDT"),
+ PAIR(TimeZone, Cuba, "Cuba"),
+ PAIR(TimeZone, Eet, "EET"),
+ PAIR(TimeZone, Egypt, "Egypt"),
+ PAIR(TimeZone, Eire, "Eire"),
+ PAIR(TimeZone, Est, "EST"),
+ PAIR(TimeZone, Est5Edt, "EST5EDT"),
+ PAIR(TimeZone, Gb, "GB"),
+ PAIR(TimeZone, GbEire, "GB-Eire"),
+ PAIR(TimeZone, Gmt, "GMT"),
+ PAIR(TimeZone, GmtPlusZero, "GMT+0"),
+ PAIR(TimeZone, GmtMinusZero, "GMT-0"),
+ PAIR(TimeZone, GmtZero, "GMT0"),
+ PAIR(TimeZone, Greenwich, "Greenwich"),
+ PAIR(TimeZone, Hongkong, "Hongkong"),
+ PAIR(TimeZone, Hst, "HST"),
+ PAIR(TimeZone, Iceland, "Iceland"),
+ PAIR(TimeZone, Iran, "Iran"),
+ PAIR(TimeZone, Israel, "Israel"),
+ PAIR(TimeZone, Jamaica, "Jamaica"),
+ PAIR(TimeZone, Japan, "Japan"),
+ PAIR(TimeZone, Kwajalein, "Kwajalein"),
+ PAIR(TimeZone, Libya, "Libya"),
+ PAIR(TimeZone, Met, "MET"),
+ PAIR(TimeZone, Mst, "MST"),
+ PAIR(TimeZone, Mst7Mdt, "MST7MDT"),
+ PAIR(TimeZone, Navajo, "Navajo"),
+ PAIR(TimeZone, Nz, "NZ"),
+ PAIR(TimeZone, NzChat, "NZ-CHAT"),
+ PAIR(TimeZone, Poland, "Poland"),
+ PAIR(TimeZone, Portugal, "Portugal"),
+ PAIR(TimeZone, Prc, "PRC"),
+ PAIR(TimeZone, Pst8Pdt, "PST8PDT"),
+ PAIR(TimeZone, Roc, "ROC"),
+ PAIR(TimeZone, Rok, "ROK"),
+ PAIR(TimeZone, Singapore, "Singapore"),
+ PAIR(TimeZone, Turkey, "Turkey"),
+ PAIR(TimeZone, Uct, "UCT"),
+ PAIR(TimeZone, Universal, "Universal"),
+ PAIR(TimeZone, Utc, "UTC"),
+ PAIR(TimeZone, WSu, "W-SU"),
+ PAIR(TimeZone, Wet, "WET"),
+ PAIR(TimeZone, Zulu, "Zulu"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(),
+ {
+ PAIR(AudioMode, Mono, "Mono"),
+ PAIR(AudioMode, Stereo, "Stereo"),
+ PAIR(AudioMode, Surround, "Surround"),
+ }});
+ translations->insert({Settings::EnumMetadata<Settings::MemoryLayout>::Index(),
+ {
+ PAIR(MemoryLayout, Memory_4Gb, "4GB DRAM (Default)"),
+ PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"),
+ PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"),
+ }});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
+ {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
+
+#undef PAIR
+#undef CTX_PAIR
+
+ return translations;
+}
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_translation.h b/src/yuzu/configuration/shared_translation.h
new file mode 100644
index 000000000..99a0e808c
--- /dev/null
+++ b/src/yuzu/configuration/shared_translation.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <typeindex>
+#include <utility>
+#include <vector>
+#include <QString>
+#include "common/common_types.h"
+
+class QWidget;
+
+namespace ConfigurationShared {
+using TranslationMap = std::map<u32, std::pair<QString, QString>>;
+using ComboboxTranslations = std::vector<std::pair<u32, QString>>;
+using ComboboxTranslationMap = std::map<u32, ComboboxTranslations>;
+
+std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent);
+
+std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent);
+
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
new file mode 100644
index 000000000..ea8d7add4
--- /dev/null
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -0,0 +1,802 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "yuzu/configuration/shared_widget.h"
+
+#include <functional>
+#include <limits>
+#include <typeindex>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include <QAbstractButton>
+#include <QAbstractSlider>
+#include <QBoxLayout>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDateTime>
+#include <QDateTimeEdit>
+#include <QIcon>
+#include <QLabel>
+#include <QLayout>
+#include <QLineEdit>
+#include <QObject>
+#include <QPushButton>
+#include <QRadioButton>
+#include <QRegularExpression>
+#include <QSizePolicy>
+#include <QSlider>
+#include <QSpinBox>
+#include <QStyle>
+#include <QValidator>
+#include <QVariant>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobjectdefs.h>
+#include <fmt/core.h>
+#include <qglobal.h>
+#include <qnamespace.h>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/settings_common.h"
+#include "yuzu/configuration/shared_translation.h"
+
+namespace ConfigurationShared {
+
+static int restore_button_count = 0;
+
+static std::string RelevantDefault(const Settings::BasicSetting& setting) {
+ return Settings::IsConfiguringGlobal() ? setting.DefaultToString() : setting.ToStringGlobal();
+}
+
+static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
+ const auto tr = [parent](const char* text, const char* context) {
+ return parent->tr(text, context);
+ };
+
+ if ((setting.Specialization() & Settings::SpecializationAttributeMask) ==
+ Settings::Specialization::Percentage) {
+ std::string context{fmt::format("{} percentage (e.g. 50%)", setting.GetLabel())};
+ return tr("%", context.c_str());
+ }
+
+ return default_suffix;
+}
+
+QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
+ restore_button_count++;
+
+ QStyle* style = parent->style();
+ QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
+ QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
+ restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
+ restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+
+ // Workaround for dark theme causing min-width to be much larger than 0
+ restore_button->setStyleSheet(
+ QStringLiteral("QAbstractButton#%1 { min-width: 0px }").arg(restore_button->objectName()));
+
+ QSizePolicy sp_retain = restore_button->sizePolicy();
+ sp_retain.setRetainSizeWhenHidden(true);
+ restore_button->setSizePolicy(sp_retain);
+
+ restore_button->setEnabled(!using_global);
+ restore_button->setVisible(!using_global);
+
+ return restore_button;
+}
+
+QLabel* Widget::CreateLabel(const QString& text) {
+ QLabel* qt_label = new QLabel(text, this->parent);
+ qt_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ return qt_label;
+}
+
+QWidget* Widget::CreateCheckBox(Settings::BasicSetting* bool_setting, const QString& label,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ checkbox = new QCheckBox(label, this);
+ checkbox->setCheckState(bool_setting->ToString() == "true" ? Qt::CheckState::Checked
+ : Qt::CheckState::Unchecked);
+ checkbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ if (!bool_setting->Save() && !Settings::IsConfiguringGlobal() && runtime_lock) {
+ checkbox->setEnabled(false);
+ }
+
+ serializer = [this]() {
+ return checkbox->checkState() == Qt::CheckState::Checked ? "true" : "false";
+ };
+
+ restore_func = [this, bool_setting]() {
+ checkbox->setCheckState(RelevantDefault(*bool_setting) == "true" ? Qt::Checked
+ : Qt::Unchecked);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(checkbox, &QCheckBox::clicked, [touch]() { touch(); });
+ }
+
+ return checkbox;
+}
+
+QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto type = setting.EnumIndex();
+
+ combobox = new QComboBox(this);
+ combobox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ const ComboboxTranslations* enumeration{nullptr};
+ if (combobox_enumerations.contains(type)) {
+ enumeration = &combobox_enumerations.at(type);
+ for (const auto& [id, name] : *enumeration) {
+ combobox->addItem(name);
+ }
+ } else {
+ return combobox;
+ }
+
+ const auto find_index = [=](u32 value) -> int {
+ for (u32 i = 0; i < enumeration->size(); i++) {
+ if (enumeration->at(i).first == value) {
+ return i;
+ }
+ }
+ return -1;
+ };
+
+ const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
+ combobox->setCurrentIndex(find_index(setting_value));
+
+ serializer = [this, enumeration]() {
+ int current = combobox->currentIndex();
+ return std::to_string(enumeration->at(current).first);
+ };
+
+ restore_func = [this, find_index]() {
+ const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
+ combobox->setCurrentIndex(find_index(global_value));
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(combobox, QOverload<int>::of(&QComboBox::activated),
+ [touch]() { touch(); });
+ }
+
+ return combobox;
+}
+
+QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto type = setting.EnumIndex();
+
+ QWidget* group = new QWidget(this);
+ QHBoxLayout* layout = new QHBoxLayout(group);
+ layout->setContentsMargins(0, 0, 0, 0);
+ group->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ const ComboboxTranslations* enumeration{nullptr};
+ if (combobox_enumerations.contains(type)) {
+ enumeration = &combobox_enumerations.at(type);
+ for (const auto& [id, name] : *enumeration) {
+ QRadioButton* radio_button = new QRadioButton(name, group);
+ layout->addWidget(radio_button);
+ radio_buttons.push_back({id, radio_button});
+ }
+ } else {
+ return group;
+ }
+
+ const auto get_selected = [=]() -> int {
+ for (const auto& [id, button] : radio_buttons) {
+ if (button->isChecked()) {
+ return id;
+ }
+ }
+ return -1;
+ };
+
+ const auto set_index = [=](u32 value) {
+ for (const auto& [id, button] : radio_buttons) {
+ button->setChecked(id == value);
+ }
+ };
+
+ const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
+ set_index(setting_value);
+
+ serializer = [get_selected]() {
+ int current = get_selected();
+ return std::to_string(current);
+ };
+
+ restore_func = [this, set_index]() {
+ const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
+ set_index(global_value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ for (const auto& [id, button] : radio_buttons) {
+ QObject::connect(button, &QAbstractButton::clicked, [touch]() { touch(); });
+ }
+ }
+
+ return group;
+}
+
+QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch, bool managed) {
+ const QString text = QString::fromStdString(setting.ToString());
+ line_edit = new QLineEdit(this);
+ line_edit->setText(text);
+
+ serializer = [this]() { return line_edit->text().toStdString(); };
+
+ if (!managed) {
+ return line_edit;
+ }
+
+ restore_func = [this]() {
+ line_edit->setText(QString::fromStdString(RelevantDefault(setting)));
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(line_edit, &QLineEdit::textChanged, [touch]() { touch(); });
+ }
+
+ return line_edit;
+}
+
+static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
+ QLabel* feedback, const QString& use_format, QSlider* slider,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func) {
+ const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
+
+ const auto update_feedback = [=](int value) {
+ int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
+ feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
+ };
+
+ QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
+ update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
+
+ slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
+ slider->setMaximum(max_val);
+ slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
+
+ serializer = [slider]() { return std::to_string(slider->value()); };
+ restore_func = [slider, &setting]() {
+ slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
+ };
+}
+
+static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
+ QLabel* feedback, const QString& use_format, QSlider* slider,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func) {
+ const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
+ const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
+ const float use_multiplier =
+ multiplier == default_multiplier ? default_float_multiplier : multiplier;
+
+ const auto update_feedback = [=](float value) {
+ int present = (reversed ? max_val - value : value) + 0.5f;
+ feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
+ };
+
+ QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
+ update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
+
+ slider->setMinimum(min_val * use_multiplier);
+ slider->setMaximum(max_val * use_multiplier);
+ slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
+
+ serializer = [slider, use_multiplier]() {
+ return std::to_string(slider->value() / use_multiplier);
+ };
+ restore_func = [slider, &setting, use_multiplier]() {
+ slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
+ };
+}
+
+QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ if (!setting.Ranged()) {
+ LOG_ERROR(Frontend, "\"{}\" is not a ranged setting, but a slider was requested.",
+ setting.GetLabel());
+ return nullptr;
+ }
+
+ QWidget* container = new QWidget(this);
+ QHBoxLayout* layout = new QHBoxLayout(container);
+
+ slider = new QSlider(Qt::Horizontal, this);
+ QLabel* feedback = new QLabel(this);
+
+ layout->addWidget(slider);
+ layout->addWidget(feedback);
+
+ container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
+
+ const QString use_format = QStringLiteral("%1").append(suffix);
+
+ if (setting.IsIntegral()) {
+ CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
+ restore_func);
+ } else {
+ CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
+ restore_func);
+ }
+
+ slider->setInvertedAppearance(reversed);
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
+ }
+
+ return container;
+}
+
+QWidget* Widget::CreateSpinBox(const QString& given_suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
+ const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
+ const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
+
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
+
+ spinbox = new QSpinBox(this);
+ spinbox->setRange(min_val, max_val);
+ spinbox->setValue(default_val);
+ spinbox->setSuffix(suffix);
+ spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serializer = [this]() { return std::to_string(spinbox->value()); };
+
+ restore_func = [this]() {
+ auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
+ spinbox->setValue(value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
+ if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
+ touch();
+ }
+ });
+ }
+
+ return spinbox;
+}
+
+QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
+ const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
+ const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
+
+ QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
+
+ double_spinbox = new QDoubleSpinBox(this);
+ double_spinbox->setRange(min_val, max_val);
+ double_spinbox->setValue(default_val);
+ double_spinbox->setSuffix(suffix);
+ double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
+
+ restore_func = [this]() {
+ auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
+ double_spinbox->setValue(value);
+ };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
+ [this, touch]() {
+ if (double_spinbox->value() !=
+ std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
+ touch();
+ }
+ });
+ }
+
+ return double_spinbox;
+}
+
+QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ auto* data_component = CreateLineEdit(serializer, restore_func, touch, false);
+ if (data_component == nullptr) {
+ return nullptr;
+ }
+
+ auto to_hex = [=](const std::string& input) {
+ return QString::fromStdString(
+ fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
+ };
+
+ QRegularExpressionValidator* regex = new QRegularExpressionValidator(
+ QRegularExpression{QStringLiteral("^[0-9a-fA-F]{0,8}$")}, line_edit);
+
+ const QString default_val = to_hex(setting.ToString());
+
+ line_edit->setText(default_val);
+ line_edit->setMaxLength(8);
+ line_edit->setValidator(regex);
+
+ auto hex_to_dec = [this]() -> std::string {
+ return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
+ };
+
+ serializer = [hex_to_dec]() { return hex_to_dec(); };
+
+ restore_func = [this, to_hex]() { line_edit->setText(to_hex(RelevantDefault(setting))); };
+
+ if (!Settings::IsConfiguringGlobal()) {
+
+ QObject::connect(line_edit, &QLineEdit::textChanged, [touch]() { touch(); });
+ }
+
+ return line_edit;
+}
+
+QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch) {
+ const long long current_time = QDateTime::currentSecsSinceEpoch();
+ const s64 the_time =
+ disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
+ const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
+
+ date_time_edit = new QDateTimeEdit(this);
+ date_time_edit->setDateTime(default_val);
+ date_time_edit->setMinimumDateTime(QDateTime::fromSecsSinceEpoch(0));
+ date_time_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ serializer = [this]() { return std::to_string(date_time_edit->dateTime().toSecsSinceEpoch()); };
+
+ auto get_clear_val = [this, restrict, current_time]() {
+ return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
+ if (restrict && checkbox->checkState() == Qt::Checked) {
+ return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
+ }
+ return current_time;
+ }());
+ };
+
+ restore_func = [this, get_clear_val]() { date_time_edit->setDateTime(get_clear_val()); };
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(date_time_edit, &QDateTimeEdit::editingFinished,
+ [this, get_clear_val, touch]() {
+ if (date_time_edit->dateTime() != get_clear_val()) {
+ touch();
+ }
+ });
+ }
+
+ return date_time_edit;
+}
+
+void Widget::SetupComponent(const QString& label, std::function<void()>& load_func, bool managed,
+ RequestType request, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix) {
+ created = true;
+ const auto type = setting.TypeId();
+
+ QLayout* layout = new QHBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ if (other_setting == nullptr) {
+ other_setting = setting.PairedSetting();
+ }
+
+ const bool require_checkbox =
+ other_setting != nullptr && other_setting->TypeId() == typeid(bool);
+
+ if (other_setting != nullptr && other_setting->TypeId() != typeid(bool)) {
+ LOG_WARNING(
+ Frontend,
+ "Extra setting \"{}\" specified but is not bool, refusing to create checkbox for it.",
+ other_setting->GetLabel());
+ }
+
+ std::function<std::string()> checkbox_serializer = []() -> std::string { return {}; };
+ std::function<void()> checkbox_restore_func = []() {};
+
+ std::function<void()> touch = []() {};
+ std::function<std::string()> serializer = []() -> std::string { return {}; };
+ std::function<void()> restore_func = []() {};
+
+ QWidget* data_component{nullptr};
+
+ request = [&]() {
+ if (request != RequestType::Default) {
+ return request;
+ }
+ switch (setting.Specialization() & Settings::SpecializationTypeMask) {
+ case Settings::Specialization::Default:
+ return RequestType::Default;
+ case Settings::Specialization::Time:
+ return RequestType::DateTimeEdit;
+ case Settings::Specialization::Hex:
+ return RequestType::HexEdit;
+ case Settings::Specialization::RuntimeList:
+ managed = false;
+ [[fallthrough]];
+ case Settings::Specialization::List:
+ return RequestType::ComboBox;
+ case Settings::Specialization::Scalar:
+ return RequestType::Slider;
+ case Settings::Specialization::Countable:
+ return RequestType::SpinBox;
+ case Settings::Specialization::Radio:
+ return RequestType::RadioGroup;
+ default:
+ break;
+ }
+ return request;
+ }();
+
+ if (!Settings::IsConfiguringGlobal() && managed) {
+ restore_button = CreateRestoreGlobalButton(setting.UsingGlobal(), this);
+
+ touch = [this]() {
+ LOG_DEBUG(Frontend, "Enabling custom setting for \"{}\"", setting.GetLabel());
+ restore_button->setEnabled(true);
+ restore_button->setVisible(true);
+ };
+ }
+
+ if (require_checkbox) {
+ QWidget* lhs =
+ CreateCheckBox(other_setting, label, checkbox_serializer, checkbox_restore_func, touch);
+ layout->addWidget(lhs);
+ } else if (setting.TypeId() != typeid(bool)) {
+ QLabel* qt_label = CreateLabel(label);
+ layout->addWidget(qt_label);
+ }
+
+ if (setting.TypeId() == typeid(bool)) {
+ data_component = CreateCheckBox(&setting, label, serializer, restore_func, touch);
+ } else if (setting.IsEnum()) {
+ if (request == RequestType::RadioGroup) {
+ data_component = CreateRadioGroup(serializer, restore_func, touch);
+ } else {
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ }
+ } else if (setting.IsIntegral()) {
+ switch (request) {
+ case RequestType::Slider:
+ case RequestType::ReverseSlider:
+ data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
+ serializer, restore_func, touch);
+ break;
+ case RequestType::Default:
+ case RequestType::LineEdit:
+ data_component = CreateLineEdit(serializer, restore_func, touch);
+ break;
+ case RequestType::DateTimeEdit:
+ data_component = CreateDateTimeEdit(other_setting->ToString() != "true", true,
+ serializer, restore_func, touch);
+ break;
+ case RequestType::SpinBox:
+ data_component = CreateSpinBox(suffix, serializer, restore_func, touch);
+ break;
+ case RequestType::HexEdit:
+ data_component = CreateHexEdit(serializer, restore_func, touch);
+ break;
+ case RequestType::ComboBox:
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+ } else if (setting.IsFloatingPoint()) {
+ switch (request) {
+ case RequestType::Default:
+ case RequestType::SpinBox:
+ data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
+ break;
+ case RequestType::Slider:
+ case RequestType::ReverseSlider:
+ data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
+ serializer, restore_func, touch);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+ } else if (type == typeid(std::string)) {
+ switch (request) {
+ case RequestType::Default:
+ case RequestType::LineEdit:
+ data_component = CreateLineEdit(serializer, restore_func, touch);
+ break;
+ case RequestType::ComboBox:
+ data_component = CreateCombobox(serializer, restore_func, touch);
+ break;
+ default:
+ UNIMPLEMENTED();
+ }
+ }
+
+ if (data_component == nullptr) {
+ LOG_ERROR(Frontend, "Failed to create widget for \"{}\"", setting.GetLabel());
+ created = false;
+ return;
+ }
+
+ layout->addWidget(data_component);
+
+ if (!managed) {
+ return;
+ }
+
+ if (Settings::IsConfiguringGlobal()) {
+ load_func = [this, serializer, checkbox_serializer, require_checkbox, other_setting]() {
+ if (require_checkbox && other_setting->UsingGlobal()) {
+ other_setting->LoadString(checkbox_serializer());
+ }
+ if (setting.UsingGlobal()) {
+ setting.LoadString(serializer());
+ }
+ };
+ } else {
+ layout->addWidget(restore_button);
+
+ QObject::connect(restore_button, &QAbstractButton::clicked,
+ [this, restore_func, checkbox_restore_func](bool) {
+ LOG_DEBUG(Frontend, "Restore global state for \"{}\"",
+ setting.GetLabel());
+
+ restore_button->setEnabled(false);
+ restore_button->setVisible(false);
+
+ checkbox_restore_func();
+ restore_func();
+ });
+
+ load_func = [this, serializer, require_checkbox, checkbox_serializer, other_setting]() {
+ bool using_global = !restore_button->isEnabled();
+ setting.SetGlobal(using_global);
+ if (!using_global) {
+ setting.LoadString(serializer());
+ }
+ if (require_checkbox) {
+ other_setting->SetGlobal(using_global);
+ if (!using_global) {
+ other_setting->LoadString(checkbox_serializer());
+ }
+ }
+ };
+ }
+
+ if (other_setting != nullptr) {
+ const auto reset = [restore_func, data_component](int state) {
+ data_component->setEnabled(state == Qt::Checked);
+ if (state != Qt::Checked) {
+ restore_func();
+ }
+ };
+ connect(checkbox, &QCheckBox::stateChanged, reset);
+ reset(checkbox->checkState());
+ }
+}
+
+bool Widget::Valid() const {
+ return created;
+}
+
+Widget::~Widget() = default;
+
+Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translations_,
+ const ComboboxTranslationMap& combobox_translations_, QWidget* parent_,
+ bool runtime_lock_, std::vector<std::function<void(bool)>>& apply_funcs_,
+ RequestType request, bool managed, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix)
+ : QWidget(parent_), parent{parent_}, translations{translations_},
+ combobox_enumerations{combobox_translations_}, setting{*setting_}, apply_funcs{apply_funcs_},
+ runtime_lock{runtime_lock_} {
+ if (!Settings::IsConfiguringGlobal() && !setting.Switchable()) {
+ LOG_DEBUG(Frontend, "\"{}\" is not switchable, skipping...", setting.GetLabel());
+ return;
+ }
+
+ const int id = setting.Id();
+
+ const auto [label, tooltip] = [&]() {
+ const auto& setting_label = setting.GetLabel();
+ if (translations.contains(id)) {
+ return std::pair{translations.at(id).first, translations.at(id).second};
+ }
+ LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
+ return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
+ }();
+
+ if (label == QStringLiteral()) {
+ LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
+ setting.GetLabel());
+ return;
+ }
+
+ std::function<void()> load_func = []() {};
+
+ SetupComponent(label, load_func, managed, request, multiplier, other_setting, suffix);
+
+ if (!created) {
+ LOG_WARNING(Frontend, "No widget was created for \"{}\"", setting.GetLabel());
+ return;
+ }
+
+ apply_funcs.push_back([load_func, setting_](bool powered_on) {
+ if (setting_->RuntimeModfiable() || !powered_on) {
+ load_func();
+ }
+ });
+
+ bool enable = runtime_lock || setting.RuntimeModfiable();
+ if (setting.Switchable() && Settings::IsConfiguringGlobal() && !runtime_lock) {
+ enable &= setting.UsingGlobal();
+ }
+ this->setEnabled(enable);
+
+ this->setToolTip(tooltip);
+}
+
+Builder::Builder(QWidget* parent_, bool runtime_lock_)
+ : translations{InitializeTranslations(parent_)},
+ combobox_translations{ComboboxEnumeration(parent_)}, parent{parent_}, runtime_lock{
+ runtime_lock_} {}
+
+Builder::~Builder() = default;
+
+Widget* Builder::BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ RequestType request, bool managed, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix) const {
+ if (!Settings::IsConfiguringGlobal() && !setting->Switchable()) {
+ return nullptr;
+ }
+
+ if (setting->Specialization() == Settings::Specialization::Paired) {
+ LOG_DEBUG(Frontend, "\"{}\" has specialization Paired: ignoring", setting->GetLabel());
+ return nullptr;
+ }
+
+ return new Widget(setting, *translations, *combobox_translations, parent, runtime_lock,
+ apply_funcs, request, managed, multiplier, other_setting, suffix);
+}
+
+Widget* Builder::BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ Settings::BasicSetting* other_setting, RequestType request,
+ const QString& suffix) const {
+ return BuildWidget(setting, apply_funcs, request, true, 1.0f, other_setting, suffix);
+}
+
+const ComboboxTranslationMap& Builder::ComboboxTranslations() const {
+ return *combobox_translations;
+}
+
+} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
new file mode 100644
index 000000000..226284cf3
--- /dev/null
+++ b/src/yuzu/configuration/shared_widget.h
@@ -0,0 +1,178 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+#include <QString>
+#include <QStringLiteral>
+#include <QWidget>
+#include <qobjectdefs.h>
+#include "yuzu/configuration/shared_translation.h"
+
+class QCheckBox;
+class QComboBox;
+class QDateTimeEdit;
+class QLabel;
+class QLineEdit;
+class QObject;
+class QPushButton;
+class QSlider;
+class QSpinBox;
+class QDoubleSpinBox;
+class QRadioButton;
+
+namespace Settings {
+class BasicSetting;
+} // namespace Settings
+
+namespace ConfigurationShared {
+
+enum class RequestType {
+ Default,
+ ComboBox,
+ SpinBox,
+ Slider,
+ ReverseSlider,
+ LineEdit,
+ HexEdit,
+ DateTimeEdit,
+ RadioGroup,
+ MaxEnum,
+};
+
+constexpr float default_multiplier{1.f};
+constexpr float default_float_multiplier{100.f};
+static const QString default_suffix = QStringLiteral();
+
+class Widget : public QWidget {
+ Q_OBJECT
+
+public:
+ /**
+ * @param setting The primary Setting to create the Widget for
+ * @param translations Map of translations to display on the left side label/checkbox
+ * @param combobox_translations Map of translations for enumerating combo boxes
+ * @param parent Qt parent
+ * @param runtime_lock Emulated guest powered on state, for use on settings that should be
+ * configured during guest execution
+ * @param apply_funcs_ List to append, functions to run to apply the widget state to the setting
+ * @param request What type of data representation component to create -- not always respected
+ * for the Setting data type
+ * @param managed Set true if the caller will set up component data and handling
+ * @param multiplier Value to multiply the slider feedback label
+ * @param other_setting Second setting to modify, to replace the label with a checkbox
+ * @param suffix Set to specify formats for Slider feedback labels or SpinBox
+ */
+ explicit Widget(Settings::BasicSetting* setting, const TranslationMap& translations,
+ const ComboboxTranslationMap& combobox_translations, QWidget* parent,
+ bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
+ RequestType request = RequestType::Default, bool managed = true,
+ float multiplier = default_multiplier,
+ Settings::BasicSetting* other_setting = nullptr,
+ const QString& suffix = default_suffix);
+ virtual ~Widget();
+
+ /**
+ * @returns True if the Widget successfully created the components for the setting
+ */
+ bool Valid() const;
+
+ /**
+ * Creates a button to appear when a setting has been modified. This exists for custom
+ * configurations and wasn't designed to work for the global configuration. It has public access
+ * for settings that need to be unmanaged but can be custom.
+ *
+ * @param using_global The global state of the setting this button is for
+ * @param parent QWidget parent
+ */
+ [[nodiscard]] static QPushButton* CreateRestoreGlobalButton(bool using_global, QWidget* parent);
+
+ // Direct handles to sub components created
+ QPushButton* restore_button{}; ///< Restore button for custom configurations
+ QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
+ QSpinBox* spinbox{};
+ QDoubleSpinBox* double_spinbox{};
+ QCheckBox* checkbox{};
+ QSlider* slider{};
+ QComboBox* combobox{};
+ QDateTimeEdit* date_time_edit{};
+ std::vector<std::pair<u32, QRadioButton*>> radio_buttons{};
+
+private:
+ void SetupComponent(const QString& label, std::function<void()>& load_func, bool managed,
+ RequestType request, float multiplier,
+ Settings::BasicSetting* other_setting, const QString& suffix);
+
+ QLabel* CreateLabel(const QString& text);
+ QWidget* CreateCheckBox(Settings::BasicSetting* bool_setting, const QString& label,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+
+ QWidget* CreateCombobox(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+ QWidget* CreateRadioGroup(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+ QWidget* CreateLineEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch,
+ bool managed = true);
+ QWidget* CreateHexEdit(std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch);
+ QWidget* CreateSlider(bool reversed, float multiplier, const QString& suffix,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch);
+ QWidget* CreateDateTimeEdit(bool disabled, bool restrict,
+ std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+ QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
+ std::function<void()>& restore_func, const std::function<void()>& touch);
+ QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
+ std::function<void()>& restore_func,
+ const std::function<void()>& touch);
+
+ QWidget* parent;
+ const TranslationMap& translations;
+ const ComboboxTranslationMap& combobox_enumerations;
+ Settings::BasicSetting& setting;
+ std::vector<std::function<void(bool)>>& apply_funcs;
+
+ bool created{false};
+ bool runtime_lock{false};
+};
+
+class Builder {
+public:
+ explicit Builder(QWidget* parent, bool runtime_lock);
+ ~Builder();
+
+ Widget* BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ RequestType request = RequestType::Default, bool managed = true,
+ float multiplier = default_multiplier,
+ Settings::BasicSetting* other_setting = nullptr,
+ const QString& suffix = default_suffix) const;
+
+ Widget* BuildWidget(Settings::BasicSetting* setting,
+ std::vector<std::function<void(bool)>>& apply_funcs,
+ Settings::BasicSetting* other_setting,
+ RequestType request = RequestType::Default,
+ const QString& suffix = default_suffix) const;
+
+ const ComboboxTranslationMap& ComboboxTranslations() const;
+
+private:
+ std::unique_ptr<TranslationMap> translations;
+ std::unique_ptr<ComboboxTranslationMap> combobox_translations;
+
+ QWidget* parent;
+ const bool runtime_lock;
+};
+
+} // namespace ConfigurationShared
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
index ac2fc1bcb..57b50abd0 100644
--- a/src/yuzu/discord_impl.cpp
+++ b/src/yuzu/discord_impl.cpp
@@ -3,9 +3,14 @@
#include <chrono>
#include <string>
+
+#include <QEventLoop>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+
#include <discord_rpc.h>
#include <fmt/format.h>
-#include <httplib.h>
+
#include "common/common_types.h"
#include "common/string_util.h"
#include "core/core.h"
@@ -31,7 +36,7 @@ void DiscordImpl::Pause() {
Discord_ClearPresence();
}
-static std::string GetGameString(const std::string& title) {
+std::string DiscordImpl::GetGameString(const std::string& title) {
// Convert to lowercase
std::string icon_name = Common::ToLower(title);
@@ -56,51 +61,56 @@ static std::string GetGameString(const std::string& title) {
return icon_name;
}
-void DiscordImpl::Update() {
+void DiscordImpl::UpdateGameStatus(bool use_default) {
+ const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
+ const std::string default_image = "yuzu_logo";
+ const std::string url = use_default ? default_image : game_url;
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
+ DiscordRichPresence presence{};
+
+ presence.largeImageKey = url.c_str();
+ presence.largeImageText = game_title.c_str();
+ presence.smallImageKey = default_image.c_str();
+ presence.smallImageText = default_text.c_str();
+ presence.state = game_title.c_str();
+ presence.details = "Currently in game";
+ presence.startTimestamp = start_time;
+ Discord_UpdatePresence(&presence);
+}
+
+void DiscordImpl::Update() {
const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
const std::string default_image = "yuzu_logo";
- std::string game_cover_url = "https://yuzu-emu.org";
- std::string title;
-
- DiscordRichPresence presence{};
if (system.IsPoweredOn()) {
- system.GetAppLoader().ReadTitle(title);
+ system.GetAppLoader().ReadTitle(game_title);
// Used to format Icon URL for yuzu website game compatibility page
- std::string icon_name = GetGameString(title);
-
- // New Check for game cover
- httplib::Client cli(game_cover_url);
- cli.set_connection_timeout(std::chrono::seconds(3));
- cli.set_read_timeout(std::chrono::seconds(3));
-
- if (auto res = cli.Head(fmt::format("/images/game/boxart/{}.png", icon_name))) {
- if (res->status == 200) {
- game_cover_url += fmt::format("/images/game/boxart/{}.png", icon_name);
- } else {
- game_cover_url = "yuzu_logo";
- }
- } else {
- game_cover_url = "yuzu_logo";
- }
-
- presence.largeImageKey = game_cover_url.c_str();
- presence.largeImageText = title.c_str();
-
- presence.smallImageKey = default_image.c_str();
- presence.smallImageText = default_text.c_str();
- presence.state = title.c_str();
- presence.details = "Currently in game";
- } else {
- presence.largeImageKey = default_image.c_str();
- presence.largeImageText = default_text.c_str();
- presence.details = "Currently not in game";
+ std::string icon_name = GetGameString(game_title);
+ game_url = fmt::format("https://yuzu-emu.org/images/game/boxart/{}.png", icon_name);
+
+ QNetworkAccessManager manager;
+ QNetworkRequest request;
+ request.setUrl(QUrl(QString::fromStdString(game_url)));
+ request.setTransferTimeout(3000);
+ QNetworkReply* reply = manager.head(request);
+ QEventLoop request_event_loop;
+ QObject::connect(reply, &QNetworkReply::finished, &request_event_loop, &QEventLoop::quit);
+ request_event_loop.exec();
+ UpdateGameStatus(reply->error());
+ return;
}
+ s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ DiscordRichPresence presence{};
+ presence.largeImageKey = default_image.c_str();
+ presence.largeImageText = default_text.c_str();
+ presence.details = "Currently not in game";
presence.startTimestamp = start_time;
Discord_UpdatePresence(&presence);
}
diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h
index 84710b9c6..eb6cf9ae0 100644
--- a/src/yuzu/discord_impl.h
+++ b/src/yuzu/discord_impl.h
@@ -19,6 +19,13 @@ public:
void Pause() override;
void Update() override;
+private:
+ std::string GetGameString(const std::string& title);
+ void UpdateGameStatus(bool use_default);
+
+ std::string game_url{};
+ std::string game_title{};
+
Core::System& system;
};
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 465084fea..74f48031a 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -214,13 +214,17 @@ void GameList::OnTextChanged(const QString& new_text) {
const int children_count = folder->rowCount();
for (int j = 0; j < children_count; ++j) {
++children_total;
+
const QStandardItem* child = folder->child(j, 0);
+
+ const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong();
+
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();
+ QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'});
// Only items which filename in combination with its title contains all words
// that are in the searchfield will be visible in the gamelist
@@ -231,7 +235,7 @@ void GameList::OnTextChanged(const QString& new_text) {
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))) {
+ (file_program_id.count() == 16 && file_program_id.contains(edit_filter_text))) {
tree_view->setRowHidden(j, folder_index, false);
++result_count;
} else {
@@ -308,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
}
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
- Core::System& system_, GMainWindow* parent)
- : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} {
+ PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
+ GMainWindow* parent)
+ : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
+ play_time_manager{play_time_manager_}, system{system_} {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -336,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
+ tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -544,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
+ QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
@@ -553,11 +561,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
+ QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
-#ifndef WIN32
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
+#ifndef WIN32
QAction* create_applications_menu_shortcut =
shortcut_menu->addAction(tr("Add to Applications Menu"));
#endif
@@ -584,10 +593,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
});
connect(start_game, &QAction::triggered, [this, path]() {
- emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal);
+ emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal,
+ AmLaunchType::UserInitiated);
});
connect(start_game_global, &QAction::triggered, [this, path]() {
- emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global);
+ emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global,
+ AmLaunchType::UserInitiated);
});
connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
@@ -615,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
});
+ connect(remove_play_time_data, &QAction::triggered,
+ [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
});
@@ -624,15 +637,17 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
});
+ connect(verify_integrity, &QAction::triggered,
+ [this, path]() { emit VerifyIntegrityRequested(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);
});
-#ifndef WIN32
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
});
+#ifndef WIN32
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
});
@@ -781,6 +796,7 @@ void GameList::RetranslateUI() {
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
+ item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
}
void GameListSearchField::changeEvent(QEvent* event) {
@@ -808,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
+ tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
@@ -816,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
emit ShouldCancelWorker();
GameListWorker* worker =
- new GameListWorker(vfs, provider, game_dirs, compatibility_list, system);
+ new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 6c2f75e53..712570cea 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -18,6 +18,7 @@
#include "core/core.h"
#include "uisettings.h"
#include "yuzu/compatibility_list.h"
+#include "yuzu/play_time_manager.h"
namespace Core {
class System;
@@ -28,6 +29,7 @@ class GameListWorker;
class GameListSearchField;
class GameListDir;
class GMainWindow;
+enum class AmLaunchType;
enum class StartGameType;
namespace FileSys {
@@ -74,11 +76,13 @@ public:
COLUMN_ADD_ONS,
COLUMN_FILE_TYPE,
COLUMN_SIZE,
+ COLUMN_PLAY_TIME,
COLUMN_COUNT, // Number of columns
};
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
- FileSys::ManualContentProvider* provider_, Core::System& system_,
+ FileSys::ManualContentProvider* provider_,
+ PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
GMainWindow* parent = nullptr);
~GameList() override;
@@ -103,7 +107,7 @@ public:
signals:
void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
- StartGameType type);
+ StartGameType type, AmLaunchType launch_type);
void GameChosen(const QString& game_path, const u64 title_id = 0);
void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
@@ -112,7 +116,9 @@ signals:
void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
+ void RemovePlayTimeRequested(u64 program_id);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
+ void VerifyIntegrityRequested(const std::string& game_path);
void CopyTIDRequested(u64 program_id);
void CreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target);
@@ -166,6 +172,7 @@ private:
friend class GameListSearchField;
+ const PlayTime::PlayTimeManager& play_time_manager;
Core::System& system;
};
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 1800f090f..86a0c41d9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -18,6 +18,7 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "yuzu/play_time_manager.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
@@ -221,6 +222,31 @@ public:
}
};
+/**
+ * GameListItem for Play Time values.
+ * This object stores the play time of a game in seconds, and its readable
+ * representation in minutes/hours
+ */
+class GameListItemPlayTime : public GameListItem {
+public:
+ static constexpr int PlayTimeRole = SortRole;
+
+ GameListItemPlayTime() = default;
+ explicit GameListItemPlayTime(const qulonglong time_seconds) {
+ setData(time_seconds, PlayTimeRole);
+ }
+
+ void setData(const QVariant& value, int role) override {
+ qulonglong time_seconds = value.toULongLong();
+ GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
+ GameListItem::setData(value, PlayTimeRole);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
+ }
+};
+
class GameListDir : public GameListItem {
public:
static constexpr int GameDirRole = Qt::UserRole + 2;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 5c910c9e0..588f1dd6e 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -191,8 +191,10 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
}
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
- const std::vector<u8>& icon, Loader::AppLoader& loader,
- u64 program_id, const CompatibilityList& compatibility_list,
+ const std::size_t size, const std::vector<u8>& icon,
+ Loader::AppLoader& loader, u64 program_id,
+ const CompatibilityList& compatibility_list,
+ const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch) {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -210,7 +212,8 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
file_type_string, program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
- new GameListItemSize(Common::FS::GetSize(path)),
+ new GameListItemSize(size),
+ new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
};
const auto patch_versions = GetGameListCachedObject(
@@ -226,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_,
- const CompatibilityList& compatibility_list_, Core::System& system_)
+ const CompatibilityList& compatibility_list_,
+ const PlayTime::PlayTimeManager& play_time_manager_,
+ Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
- compatibility_list{compatibility_list_}, system{system_} {}
+ compatibility_list{compatibility_list_},
+ play_time_manager{play_time_manager_}, system{system_} {}
GameListWorker::~GameListWorker() = default;
@@ -265,7 +271,11 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
std::vector<u8> icon;
std::string name;
u64 program_id = 0;
- loader->ReadProgramId(program_id);
+ const auto result = loader->ReadProgramId(program_id);
+
+ if (result != Loader::ResultStatus::Success) {
+ continue;
+ }
const PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
@@ -274,8 +284,8 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
GetMetadataFromControlNCA(patch, *control, icon, name);
}
- emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
- compatibility_list, patch),
+ emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
+ program_id, compatibility_list, play_time_manager, patch),
parent_dir);
}
}
@@ -350,8 +360,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, id,
- compatibility_list, patch),
+ emit EntryReady(MakeGameListEntry(physical_name, name,
+ Common::FS::GetSize(physical_name), icon,
+ *loader, id, compatibility_list,
+ play_time_manager, patch),
parent_dir);
}
} else {
@@ -364,8 +376,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader,
- program_id, compatibility_list, patch),
+ emit EntryReady(MakeGameListEntry(physical_name, name,
+ Common::FS::GetSize(physical_name), icon,
+ *loader, program_id, compatibility_list,
+ play_time_manager, patch),
parent_dir);
}
}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..2bb0a0cb6 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -13,6 +13,7 @@
#include <QString>
#include "yuzu/compatibility_list.h"
+#include "yuzu/play_time_manager.h"
namespace Core {
class System;
@@ -36,7 +37,9 @@ public:
explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_,
- const CompatibilityList& compatibility_list_, Core::System& system_);
+ const CompatibilityList& compatibility_list_,
+ const PlayTime::PlayTimeManager& play_time_manager_,
+ Core::System& system_);
~GameListWorker() override;
/// Starts the processing of directory tree information.
@@ -76,6 +79,7 @@ private:
FileSys::ManualContentProvider* provider;
QVector<UISettings::GameDir>& game_dirs;
const CompatibilityList& compatibility_list;
+ const PlayTime::PlayTimeManager& play_time_manager;
QStringList watch_list;
std::atomic_bool stop_processing;
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 848239c35..56eee8d82 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,10 +4,12 @@
#pragma once
#include <map>
+#include <QKeySequence>
+#include <QString>
+#include <QWidget>
#include "core/hid/hid_types.h"
class QDialog;
-class QKeySequence;
class QSettings;
class QShortcut;
class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 6cd557c29..5427758c1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -8,6 +8,8 @@
#include <iostream>
#include <memory>
#include <thread>
+#include "core/loader/nca.h"
+#include "core/tools/renderdoc.h"
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
@@ -16,6 +18,8 @@
#include <sys/socket.h>
#endif
+#include <boost/container/flat_set.hpp>
+
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_amiibo_settings.h"
#include "applets/qt_controller.h"
@@ -24,6 +28,7 @@
#include "applets/qt_software_keyboard.h"
#include "applets/qt_web_browser.h"
#include "common/nvidia_flags.h"
+#include "common/settings_enums.h"
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h"
@@ -93,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#ifdef _WIN32
+#include <shlobj.h>
#include "common/windows/timer_resolution.h"
#endif
#ifdef ARCHITECTURE_x86_64
@@ -145,6 +151,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/install_dialog.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
+#include "yuzu/play_time_manager.h"
#include "yuzu/startup_checks.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/clickable_label.h"
@@ -333,6 +340,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
discord_rpc->Update();
+ play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
+
system->GetRoomNetwork().Init();
RegisterMetaTypes();
@@ -441,8 +450,13 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
"here for instructions to fix the issue</a>."));
+#ifdef HAS_OPENGL
Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
+#else
+ Settings::values.renderer_backend = Settings::RendererBackend::Null;
+#endif
+ UpdateAPIText();
renderer_status_button->setDisabled(true);
renderer_status_button->setChecked(false);
} else {
@@ -976,7 +990,7 @@ void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
render_window->hide();
- game_list = new GameList(vfs, provider.get(), *system, this);
+ game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
ui->horizontalLayout->addWidget(game_list);
game_list_placeholder = new GameListPlaceholder(this);
@@ -1095,10 +1109,9 @@ void GMainWindow::InitializeWidgets() {
aa_status_button->setFocusPolicy(Qt::NoFocus);
connect(aa_status_button, &QPushButton::clicked, [&] {
auto aa_mode = Settings::values.anti_aliasing.GetValue();
- if (aa_mode == Settings::AntiAliasing::LastAA) {
+ aa_mode = static_cast<Settings::AntiAliasing>(static_cast<u32>(aa_mode) + 1);
+ if (aa_mode == Settings::AntiAliasing::MaxEnum) {
aa_mode = Settings::AntiAliasing::None;
- } else {
- aa_mode = static_cast<Settings::AntiAliasing>(static_cast<u32>(aa_mode) + 1);
}
Settings::values.anti_aliasing.SetValue(aa_mode);
aa_status_button->setChecked(true);
@@ -1158,9 +1171,9 @@ void GMainWindow::InitializeWidgets() {
[this](const QPoint& menu_location) {
QMenu context_menu;
- for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) {
- context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] {
- if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) {
+ for (auto const& pair : Config::use_docked_mode_texts_map) {
+ context_menu.addAction(pair.second, [this, &pair] {
+ if (pair.first != Settings::values.use_docked_mode.GetValue()) {
OnToggleDockedMode();
}
});
@@ -1183,7 +1196,7 @@ void GMainWindow::InitializeWidgets() {
QMenu context_menu;
for (auto const& gpu_accuracy_pair : Config::gpu_accuracy_texts_map) {
- if (gpu_accuracy_pair.first == Settings::GPUAccuracy::Extreme) {
+ if (gpu_accuracy_pair.first == Settings::GpuAccuracy::Extreme) {
continue;
}
context_menu.addAction(gpu_accuracy_pair.second, [this, gpu_accuracy_pair] {
@@ -1342,6 +1355,11 @@ void GMainWindow::InitializeHotkeys() {
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
});
+ connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
+ if (Settings::values.enable_renderdoc_hotkey) {
+ system->GetRenderdocAPI().ToggleCapture();
+ }
+ });
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
if (Settings::values.mouse_enabled) {
Settings::values.mouse_panning = false;
@@ -1433,6 +1451,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
Settings::values.audio_muted = false;
auto_muted = false;
}
+ UpdateVolumeUI();
}
}
@@ -1446,7 +1465,11 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
&GMainWindow::OnGameListRemoveInstalledEntry);
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
+ connect(game_list, &GameList::RemovePlayTimeRequested, this,
+ &GMainWindow::OnGameListRemovePlayTimeData);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
+ connect(game_list, &GameList::VerifyIntegrityRequested, this,
+ &GMainWindow::OnGameListVerifyIntegrity);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
@@ -1537,6 +1560,16 @@ void GMainWindow::ConnectMenuEvents() {
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
ReinitializeKeyBehavior::Warning));
+ connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
+ connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
+ connect_menu(ui->action_Load_Cabinet_Eraser,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); });
+ connect_menu(ui->action_Load_Cabinet_Restorer,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); });
+ connect_menu(ui->action_Load_Cabinet_Formatter,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
+ connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
// TAS
@@ -1547,11 +1580,13 @@ void GMainWindow::ConnectMenuEvents() {
// Help
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
+ connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
connect_menu(ui->action_About, &GMainWindow::OnAbout);
}
void GMainWindow::UpdateMenuState() {
const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
+ const bool is_firmware_available = CheckFirmwarePresence();
const std::array running_actions{
ui->action_Stop,
@@ -1562,10 +1597,23 @@ void GMainWindow::UpdateMenuState() {
ui->action_Pause,
};
+ const std::array applet_actions{
+ ui->action_Load_Album,
+ ui->action_Load_Cabinet_Nickname_Owner,
+ ui->action_Load_Cabinet_Eraser,
+ ui->action_Load_Cabinet_Restorer,
+ ui->action_Load_Cabinet_Formatter,
+ ui->action_Load_Mii_Edit,
+ };
+
for (QAction* action : running_actions) {
action->setEnabled(emulation_running);
}
+ for (QAction* action : applet_actions) {
+ action->setEnabled(is_firmware_available && !emulation_running);
+ }
+
ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
if (emulation_running && is_paused) {
@@ -1698,7 +1746,8 @@ void GMainWindow::AllowOSSleep() {
#endif
}
-bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) {
+bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
+ AmLaunchType launch_type) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr) {
ShutdownGame();
@@ -1710,6 +1759,10 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
system->SetFilesystem(vfs);
+ if (launch_type == AmLaunchType::UserInitiated) {
+ system->GetUserChannel().clear();
+ }
+
system->SetAppletFrontendSet({
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
(UISettings::values.controller_applet_disabled.GetValue() == true)
@@ -1811,8 +1864,45 @@ bool GMainWindow::SelectAndSetCurrentUser(
return true;
}
+void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
+ // Ensure all NCAs are registered before launching the game
+ const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read);
+ if (!file) {
+ return;
+ }
+
+ auto loader = Loader::GetLoader(*system, file);
+ if (!loader) {
+ return;
+ }
+
+ const auto file_type = loader->GetFileType();
+ if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
+ return;
+ }
+
+ u64 program_id = 0;
+ const auto res2 = loader->ReadProgramId(program_id);
+ if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
+ provider->AddEntry(FileSys::TitleType::Application,
+ FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id,
+ file);
+ } else if (res2 == Loader::ResultStatus::Success &&
+ (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
+ const auto nsp = file_type == Loader::FileType::NSP
+ ? std::make_shared<FileSys::NSP>(file)
+ : FileSys::XCI{file}.GetSecurePartitionNSP();
+ for (const auto& title : nsp->GetNCAs()) {
+ for (const auto& entry : title.second) {
+ provider->AddEntry(entry.first.first, entry.first.second, title.first,
+ entry.second->GetBaseFile());
+ }
+ }
+ }
+}
+
void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
- StartGameType type) {
+ StartGameType type, AmLaunchType launch_type) {
LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
@@ -1825,6 +1915,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
last_filename_booted = filename;
+ ConfigureFilesystemProvider(filename.toStdString());
const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
const auto loader = Loader::GetLoader(*system, v_file, program_id, program_index);
@@ -1855,7 +1946,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
}
}
- if (!LoadROM(filename, program_id, program_index)) {
+ if (!LoadROM(filename, program_id, program_index, launch_type)) {
return;
}
@@ -1972,8 +2063,16 @@ bool GMainWindow::OnShutdownBegin() {
emit EmulationStopping();
+ int shutdown_time = 1000;
+
+ if (system->DebuggerEnabled()) {
+ shutdown_time = 0;
+ } else if (system->GetExitLocked()) {
+ shutdown_time = 5000;
+ }
+
shutdown_timer.setSingleShot(true);
- shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000);
+ shutdown_timer.start(shutdown_time);
connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
@@ -2034,6 +2133,8 @@ void GMainWindow::OnEmulationStopped() {
OnTasStateChanged();
render_window->FinalizeCamera();
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None);
+
// Enable all controllers
system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -2229,40 +2330,62 @@ void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
}
-static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {
- std::size_t out = 0;
-
- for (const auto& subdir : dir->GetSubdirectories()) {
- out += 1 + CalculateRomFSEntrySize(subdir, full);
- }
-
- return out + (full ? dir->GetFiles().size() : 0);
-}
-
-static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src,
- const FileSys::VirtualDir& dest, std::size_t block_size, bool full) {
+static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog,
+ const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest,
+ bool full) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
return false;
if (dialog.wasCanceled())
return false;
+ std::vector<u8> buffer(CopyBufferSize);
+ auto last_timestamp = std::chrono::steady_clock::now();
+
+ const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file,
+ const FileSys::VirtualFile& dest_file) {
+ if (src_file == nullptr || dest_file == nullptr) {
+ return false;
+ }
+ if (!dest_file->Resize(src_file->GetSize())) {
+ return false;
+ }
+
+ for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) {
+ if (dialog.wasCanceled()) {
+ dest_file->Resize(0);
+ return false;
+ }
+
+ using namespace std::literals::chrono_literals;
+ const auto new_timestamp = std::chrono::steady_clock::now();
+
+ if ((new_timestamp - last_timestamp) > 33ms) {
+ last_timestamp = new_timestamp;
+ dialog.setValue(
+ static_cast<int>(std::min(read_size, total_size) * 100 / total_size));
+ QCoreApplication::processEvents();
+ }
+
+ const auto read = src_file->Read(buffer.data(), buffer.size(), i);
+ dest_file->Write(buffer.data(), read, i);
+
+ read_size += read;
+ }
+
+ return true;
+ };
+
if (full) {
for (const auto& file : src->GetFiles()) {
const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
- if (!FileSys::VfsRawCopy(file, out, block_size))
- return false;
- dialog.setValue(dialog.value() + 1);
- if (dialog.wasCanceled())
+ if (!QtRawCopy(file, out))
return false;
}
}
for (const auto& dir : src->GetSubdirectories()) {
const auto out = dest->CreateSubdirectory(dir->GetName());
- if (!RomFSRawCopy(dialog, dir, out, block_size, full))
- return false;
- dialog.setValue(dialog.value() + 1);
- if (dialog.wasCanceled())
+ if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full))
return false;
}
@@ -2420,6 +2543,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
}
}
+void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
+ if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ play_time_manager->ResetProgramPlayTime(program_id);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+}
+
void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
const auto target_file_name = [target] {
switch (target) {
@@ -2535,16 +2669,34 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- FileSys::VirtualFile file;
- if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
+ FileSys::VirtualFile packed_update_raw{};
+ loader->ReadUpdateRaw(packed_update_raw);
+
+ const auto& installed = system->GetContentProvider();
+
+ u64 title_id{};
+ u8 raw_type{};
+ if (!SelectRomFSDumpTarget(installed, program_id, &title_id, &raw_type)) {
failed();
return;
}
- const auto& installed = system->GetContentProvider();
- const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
+ const auto type = static_cast<FileSys::ContentRecordType>(raw_type);
+ const auto base_nca = installed.GetEntry(title_id, type);
+ if (!base_nca) {
+ failed();
+ return;
+ }
- if (!romfs_title_id) {
+ const FileSys::NCA update_nca{packed_update_raw, nullptr};
+ if (type != FileSys::ContentRecordType::Program ||
+ update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
+ update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
+ packed_update_raw = {};
+ }
+
+ const auto base_romfs = base_nca->GetRomFS();
+ if (!base_romfs) {
failed();
return;
}
@@ -2553,26 +2705,12 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
target == DumpRomFSTarget::Normal
? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
: Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";
- const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id);
+ const auto romfs_dir = fmt::format("{:016X}/romfs", title_id);
const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir);
- FileSys::VirtualFile romfs;
-
- if (*romfs_title_id == program_id) {
- const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
- const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed};
- romfs =
- pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false);
- } else {
- romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
- }
-
- const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
- if (extracted == nullptr) {
- failed();
- return;
- }
+ const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed};
+ auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false);
const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
@@ -2596,11 +2734,16 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
+ const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
+ if (extracted == nullptr) {
+ failed();
+ return;
+ }
+
const auto full = res == selections.constFirst();
- const auto entry_size = CalculateRomFSEntrySize(extracted, full);
- // The minimum required space is the size of the extracted RomFS + 1 GiB
- const auto minimum_free_space = extracted->GetSize() + 0x40000000;
+ // The expected required space is the size of the RomFS + 1 GiB
+ const auto minimum_free_space = romfs->GetSize() + 0x40000000;
if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
@@ -2611,12 +2754,15 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0,
- static_cast<s32>(entry_size), this);
+ QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, 100, this);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
- if (RomFSRawCopy(progress, extracted, out, 0x400000, full)) {
+ size_t read_size = 0;
+
+ if (RomFSRawCopy(romfs->GetSize(), read_size, progress, extracted, out, full)) {
progress.close();
QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
tr("The operation completed successfully."));
@@ -2628,6 +2774,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
}
}
+void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) {
+ const auto NotImplemented = [this] {
+ QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"),
+ tr("File contents were not checked for validity."));
+ };
+ const auto Failed = [this] {
+ QMessageBox::critical(this, tr("Integrity verification failed!"),
+ tr("File contents may be corrupt."));
+ };
+
+ const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
+ if (loader == nullptr) {
+ NotImplemented();
+ return;
+ }
+
+ QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) {
+ if (progress.wasCanceled()) {
+ return false;
+ }
+
+ progress.setValue(static_cast<int>((processed_size * 100) / total_size));
+ return true;
+ };
+
+ const auto status = loader->VerifyIntegrity(QtProgressCallback);
+ if (progress.wasCanceled() ||
+ status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) {
+ NotImplemented();
+ return;
+ }
+
+ if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) {
+ Failed();
+ return;
+ }
+
+ progress.close();
+ QMessageBox::information(this, tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+}
+
void GMainWindow::OnGameListCopyTID(u64 program_id) {
QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
@@ -2651,7 +2845,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const QStringList args = QApplication::arguments();
std::filesystem::path yuzu_command = args[0].toStdString();
-#if defined(__linux__) || defined(__FreeBSD__)
// If relative path, make it an absolute path
if (yuzu_command.c_str()[0] == '.') {
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2674,12 +2867,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
-#endif // __linux__ || __FreeBSD__
std::filesystem::path target_directory{};
// Determine target directory for shortcut
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(WIN32)
+ const char* home = std::getenv("USERPROFILE");
+#else
const char* home = std::getenv("HOME");
+#endif
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
@@ -2689,7 +2884,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
QMessageBox::critical(
this, tr("Create Shortcut"),
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
- .arg(QString::fromStdString(target_directory)),
+ .arg(QString::fromStdString(target_directory.generic_string())),
QMessageBox::StandardButton::Ok);
return;
}
@@ -2697,15 +2892,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
"applications";
if (!Common::FS::CreateDirs(target_directory)) {
- QMessageBox::critical(this, tr("Create Shortcut"),
- tr("Cannot create shortcut in applications menu. Path \"%1\" "
- "does not exist and cannot be created.")
- .arg(QString::fromStdString(target_directory)),
- QMessageBox::StandardButton::Ok);
+ QMessageBox::critical(
+ this, tr("Create Shortcut"),
+ tr("Cannot create shortcut in applications menu. Path \"%1\" "
+ "does not exist and cannot be created.")
+ .arg(QString::fromStdString(target_directory.generic_string())),
+ QMessageBox::StandardButton::Ok);
return;
}
}
-#endif
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
@@ -2727,9 +2922,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const std::filesystem::path shortcut_path =
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
: fmt::format("yuzu-{:016X}.desktop", program_id));
+#elif defined(WIN32)
+ std::filesystem::path icons_path =
+ Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
+ std::filesystem::path icon_path =
+ icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
+ : fmt::format("yuzu-{:016X}.ico", program_id)));
#else
- const std::filesystem::path icon_path{};
- const std::filesystem::path shortcut_path{};
+ std::string icon_extension;
#endif
// Get title from game file
@@ -2754,29 +2954,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
}
- QImage icon_jpeg =
+ QImage icon_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
#if defined(__linux__) || defined(__FreeBSD__)
// Convert and write the icon as a PNG
- if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
+ if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
+#elif defined(WIN32)
+ if (!SaveIconToFile(icon_path.string(), icon_data)) {
+ LOG_ERROR(Frontend, "Could not write icon to file");
+ return;
+ }
#endif // __linux__
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef _WIN32
+ // Replace characters that are illegal in Windows filenames by a dash
+ const std::string illegal_chars = "<>:\"/\\|?*";
+ for (char c : illegal_chars) {
+ std::replace(title.begin(), title.end(), c, '_');
+ }
+ const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
+#endif
+
const std::string comment =
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
-#else
- const std::string comment{};
- const std::string arguments{};
- const std::string categories{};
- const std::string keywords{};
-#endif
+
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
yuzu_command.string(), arguments, categories, keywords)) {
QMessageBox::critical(this, tr("Create Shortcut"),
@@ -2962,10 +3170,9 @@ void GMainWindow::OnMenuInstallToNAND() {
QFuture<InstallResult> future;
InstallResult result;
- if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
- file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
+ if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
- future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); });
+ future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
while (!future.isFinished()) {
QCoreApplication::processEvents();
@@ -3024,7 +3231,7 @@ void GMainWindow::OnMenuInstallToNAND() {
ui->action_Install_File_NAND->setEnabled(true);
}
-InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
+InstallResult GMainWindow::InstallNSP(const QString& filename) {
const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
const FileSys::VirtualFile& dest, std::size_t block_size) {
if (src == nullptr || dest == nullptr) {
@@ -3058,9 +3265,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
return InstallResult::Failure;
}
} else {
- const auto xci = std::make_shared<FileSys::XCI>(
- vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- nsp = xci->GetSecurePartitionNSP();
+ return InstallResult::Failure;
}
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
@@ -3186,6 +3391,9 @@ void GMainWindow::OnStartGame() {
UpdateMenuState();
OnTasStateChanged();
+ play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
+ play_time_manager->Start();
+
discord_rpc->Update();
}
@@ -3201,6 +3409,7 @@ void GMainWindow::OnRestartGame() {
void GMainWindow::OnPauseGame() {
emu_thread->SetRunning(false);
+ play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
}
@@ -3217,10 +3426,13 @@ void GMainWindow::OnPauseContinueGame() {
}
void GMainWindow::OnStopGame() {
- if (system->GetExitLock() && !ConfirmForceLockedExit()) {
+ if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
return;
}
+ play_time_manager->Stop();
+ // Update game list to show new play time
+ game_list->PopulateAsync(UISettings::values.game_dirs);
if (OnShutdownBegin()) {
OnShutdownBeginDialog();
} else {
@@ -3234,7 +3446,8 @@ void GMainWindow::OnLoadComplete() {
void GMainWindow::OnExecuteProgram(std::size_t program_index) {
ShutdownGame();
- BootGame(last_filename_booted, 0, program_index);
+ BootGame(last_filename_booted, 0, program_index, StartGameType::Normal,
+ AmLaunchType::ApplicationInitiated);
}
void GMainWindow::OnExit() {
@@ -3630,7 +3843,7 @@ void GMainWindow::OnTasReset() {
}
void GMainWindow::OnToggleDockedMode() {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ const bool is_docked = Settings::IsDockedMode();
auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
@@ -3644,21 +3857,22 @@ void GMainWindow::OnToggleDockedMode() {
controller_dialog->refreshConfiguration();
}
- Settings::values.use_docked_mode.SetValue(!is_docked);
+ Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld
+ : Settings::ConsoleMode::Docked);
UpdateDockedButton();
OnDockedModeChanged(is_docked, !is_docked, *system);
}
void GMainWindow::OnToggleGpuAccuracy() {
switch (Settings::values.gpu_accuracy.GetValue()) {
- case Settings::GPUAccuracy::High: {
- Settings::values.gpu_accuracy.SetValue(Settings::GPUAccuracy::Normal);
+ case Settings::GpuAccuracy::High: {
+ Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::Normal);
break;
}
- case Settings::GPUAccuracy::Normal:
- case Settings::GPUAccuracy::Extreme:
+ case Settings::GpuAccuracy::Normal:
+ case Settings::GpuAccuracy::Extreme:
default: {
- Settings::values.gpu_accuracy.SetValue(Settings::GPUAccuracy::High);
+ Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::High);
break;
}
}
@@ -3702,10 +3916,9 @@ void GMainWindow::OnIncreaseVolume() {
void GMainWindow::OnToggleAdaptingFilter() {
auto filter = Settings::values.scaling_filter.GetValue();
- if (filter == Settings::ScalingFilter::LastFilter) {
+ filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1);
+ if (filter == Settings::ScalingFilter::MaxEnum) {
filter = Settings::ScalingFilter::NearestNeighbor;
- } else {
- filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1);
}
Settings::values.scaling_filter.SetValue(filter);
filter_status_button->setChecked(true);
@@ -3714,10 +3927,14 @@ void GMainWindow::OnToggleAdaptingFilter() {
void GMainWindow::OnToggleGraphicsAPI() {
auto api = Settings::values.renderer_backend.GetValue();
- if (api == Settings::RendererBackend::OpenGL) {
+ if (api != Settings::RendererBackend::Vulkan) {
api = Settings::RendererBackend::Vulkan;
} else {
+#ifdef HAS_OPENGL
api = Settings::RendererBackend::OpenGL;
+#else
+ api = Settings::RendererBackend::Null;
+#endif
}
Settings::values.renderer_backend.SetValue(api);
renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan);
@@ -3789,6 +4006,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
shortcut_stream.close();
return true;
+#elif defined(WIN32)
+ IShellLinkW* shell_link;
+ auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
+ (void**)&shell_link);
+ if (FAILED(hres)) {
+ return false;
+ }
+ shell_link->SetPath(
+ Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
+ shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+ shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
+ shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
+
+ IPersistFile* persist_file;
+ hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
+ if (FAILED(hres)) {
+ return false;
+ }
+
+ hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
+ if (FAILED(hres)) {
+ return false;
+ }
+
+ persist_file->Release();
+ shell_link->Release();
+
+ return true;
#endif
return false;
}
@@ -3861,6 +4106,108 @@ void GMainWindow::OnOpenYuzuFolder() {
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir))));
}
+void GMainWindow::OnVerifyInstalledContents() {
+ // Declare sizes.
+ size_t total_size = 0;
+ size_t processed_size = 0;
+
+ // Initialize a progress dialog.
+ QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ // Declare a list of file names which failed to verify.
+ std::vector<std::string> failed;
+
+ // Declare progress callback.
+ auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) {
+ if (progress.wasCanceled()) {
+ return false;
+ }
+ progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size));
+ return true;
+ };
+
+ // Get content registries.
+ auto bis_contents = system->GetFileSystemController().GetSystemNANDContents();
+ auto user_contents = system->GetFileSystemController().GetUserNANDContents();
+
+ std::vector<FileSys::RegisteredCache*> content_providers;
+ if (bis_contents) {
+ content_providers.push_back(bis_contents);
+ }
+ if (user_contents) {
+ content_providers.push_back(user_contents);
+ }
+
+ // Get associated NCA files.
+ std::vector<FileSys::VirtualFile> nca_files;
+
+ // Get all installed IDs.
+ for (auto nca_provider : content_providers) {
+ const auto entries = nca_provider->ListEntriesFilter();
+
+ for (const auto& entry : entries) {
+ auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type);
+ if (!nca_file) {
+ continue;
+ }
+
+ total_size += nca_file->GetSize();
+ nca_files.push_back(std::move(nca_file));
+ }
+ }
+
+ // Using the NCA loader, determine if all NCAs are valid.
+ for (auto& nca_file : nca_files) {
+ Loader::AppLoader_NCA nca_loader(nca_file);
+
+ auto status = nca_loader.VerifyIntegrity(QtProgressCallback);
+ if (progress.wasCanceled()) {
+ break;
+ }
+ if (status != Loader::ResultStatus::Success) {
+ FileSys::NCA nca(nca_file);
+ const auto title_id = nca.GetTitleId();
+ std::string title_name = "unknown";
+
+ const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id),
+ FileSys::ContentRecordType::Control);
+ if (control && control->GetStatus() == Loader::ResultStatus::Success) {
+ const FileSys::PatchManager pm{title_id, system->GetFileSystemController(),
+ *provider};
+ const auto [nacp, logo] = pm.ParseControlNCA(*control);
+ if (nacp) {
+ title_name = nacp->GetApplicationName();
+ }
+ }
+
+ if (title_id > 0) {
+ failed.push_back(
+ fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name));
+ } else {
+ failed.push_back(fmt::format("{} (unknown)", nca_file->GetName()));
+ }
+ }
+
+ processed_size += nca_file->GetSize();
+ }
+
+ progress.close();
+
+ if (failed.size() > 0) {
+ auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n")));
+ QMessageBox::critical(
+ this, tr("Integrity verification failed!"),
+ tr("Verification failed for the following files:\n\n%1").arg(failed_names));
+ } else {
+ QMessageBox::information(this, tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+ }
+}
+
void GMainWindow::OnAbout() {
AboutDialog aboutDialog(this);
aboutDialog.exec();
@@ -3879,6 +4226,76 @@ void GMainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
+void GMainWindow::OnAlbum() {
+ constexpr u64 AlbumId = 0x010000000000100Dull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Album applet."));
+ return;
+ }
+
+ auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
+ if (!album_nca) {
+ QMessageBox::warning(this, tr("Album Applet"),
+ tr("Album applet is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
+
+ const auto filename = QString::fromStdString(album_nca->GetFullPath());
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
+void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
+ constexpr u64 CabinetId = 0x0100000000001002ull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Cabinet applet."));
+ return;
+ }
+
+ auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program);
+ if (!cabinet_nca) {
+ QMessageBox::warning(this, tr("Cabinet Applet"),
+ tr("Cabinet applet is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet);
+ system->GetAppletManager().SetCabinetMode(mode);
+
+ const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
+void GMainWindow::OnMiiEdit() {
+ constexpr u64 MiiEditId = 0x0100000000001009ull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Mii editor."));
+ return;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ if (!mii_applet_nca) {
+ QMessageBox::warning(this, tr("Mii Edit Applet"),
+ tr("Mii editor is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
+
+ const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
void GMainWindow::OnCaptureScreenshot() {
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
@@ -4071,14 +4488,14 @@ void GMainWindow::UpdateGPUAccuracyButton() {
const auto gpu_accuracy = Settings::values.gpu_accuracy.GetValue();
const auto gpu_accuracy_text = Config::gpu_accuracy_texts_map.find(gpu_accuracy)->second;
gpu_accuracy_button->setText(gpu_accuracy_text.toUpper());
- gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GPUAccuracy::Normal);
+ gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GpuAccuracy::Normal);
}
void GMainWindow::UpdateDockedButton() {
- const bool is_docked = Settings::values.use_docked_mode.GetValue();
- dock_status_button->setChecked(is_docked);
+ const auto console_mode = Settings::values.use_docked_mode.GetValue();
+ dock_status_button->setChecked(Settings::IsDockedMode());
dock_status_button->setText(
- Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper());
+ Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
}
void GMainWindow::UpdateAPIText() {
@@ -4285,6 +4702,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (behavior == ReinitializeKeyBehavior::Warning) {
game_list->PopulateAsync(UISettings::values.game_dirs);
}
+
+ UpdateMenuState();
}
bool GMainWindow::CheckSystemArchiveDecryption() {
@@ -4306,28 +4725,63 @@ bool GMainWindow::CheckSystemArchiveDecryption() {
return mii_nca->GetRomFS().get() != nullptr;
}
-std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
- u64 program_id) {
- const auto dlc_entries =
- installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
- std::vector<FileSys::ContentProviderEntry> dlc_match;
- dlc_match.reserve(dlc_entries.size());
- std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
- return FileSys::GetBaseTitleID(entry.title_id) == program_id &&
- installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
- });
-
- std::vector<u64> romfs_tids;
- romfs_tids.push_back(program_id);
- for (const auto& entry : dlc_match) {
- romfs_tids.push_back(entry.title_id);
- }
-
- if (romfs_tids.size() > 1) {
- QStringList list{QStringLiteral("Base")};
- for (std::size_t i = 1; i < romfs_tids.size(); ++i) {
- list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
+bool GMainWindow::CheckFirmwarePresence() {
+ constexpr u64 MiiEditId = 0x0100000000001009ull;
+
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ if (!mii_applet_nca) {
+ return false;
+ }
+
+ return true;
+}
+
+bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
+ u64* selected_title_id, u8* selected_content_record_type) {
+ using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
+ boost::container::flat_set<ContentInfo> available_title_ids;
+
+ const auto RetrieveEntries = [&](FileSys::TitleType title_type,
+ FileSys::ContentRecordType record_type) {
+ const auto entries = installed.ListEntriesFilter(title_type, record_type);
+ for (const auto& entry : entries) {
+ if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
+ installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
+ available_title_ids.insert({entry.title_id, title_type, record_type});
+ }
+ }
+ };
+
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument);
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation);
+ RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+
+ if (available_title_ids.empty()) {
+ return false;
+ }
+
+ size_t title_index = 0;
+
+ if (available_title_ids.size() > 1) {
+ QStringList list;
+ for (auto& [title_id, title_type, record_type] : available_title_ids) {
+ const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
+ if (record_type == FileSys::ContentRecordType::Program) {
+ list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id));
+ } else if (record_type == FileSys::ContentRecordType::HtmlDocument) {
+ list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id));
+ } else if (record_type == FileSys::ContentRecordType::LegalInformation) {
+ list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id));
+ } else {
+ list.push_back(
+ QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
+ }
}
bool ok;
@@ -4335,13 +4789,16 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv
this, tr("Select RomFS Dump Target"),
tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
if (!ok) {
- return {};
+ return false;
}
- return romfs_tids[list.indexOf(res)];
+ title_index = list.indexOf(res);
}
- return program_id;
+ const auto& [title_id, title_type, record_type] = *available_title_ids.nth(title_index);
+ *selected_title_id = title_id;
+ *selected_content_record_type = static_cast<u8>(record_type);
+ return true;
}
bool GMainWindow::ConfirmClose() {
@@ -4471,6 +4928,8 @@ void GMainWindow::RequestGameExit() {
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
+ system->SetExitRequested(true);
+
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->RequestExit();
has_signalled = true;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 2cfb96257..2346eb3bd 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -58,6 +58,11 @@ enum class StartGameType {
Global, // Only uses global configuration
};
+enum class AmLaunchType {
+ UserInitiated,
+ ApplicationInitiated,
+};
+
namespace Core {
enum class SystemResultStatus : u32;
class System;
@@ -76,6 +81,10 @@ namespace DiscordRPC {
class DiscordInterface;
}
+namespace PlayTime {
+class PlayTimeManager;
+}
+
namespace FileSys {
class ContentProvider;
class ManualContentProvider;
@@ -97,6 +106,10 @@ namespace Service::NFC {
class NfcDevice;
} // namespace Service::NFC
+namespace Service::NFP {
+enum class CabinetMode : u8;
+} // namespace Service::NFP
+
namespace Ui {
class MainWindow;
}
@@ -239,9 +252,11 @@ private:
void PreventOSSleep();
void AllowOSSleep();
- bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index);
+ bool LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
+ AmLaunchType launch_type);
void BootGame(const QString& filename, u64 program_id = 0, std::size_t program_index = 0,
- StartGameType with_config = StartGameType::Normal);
+ StartGameType with_config = StartGameType::Normal,
+ AmLaunchType launch_type = AmLaunchType::UserInitiated);
void ShutdownGame();
void ShowTelemetryCallout();
@@ -312,7 +327,9 @@ private slots:
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
+ void OnGameListRemovePlayTimeData(u64 program_id);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
+ void OnGameListVerifyIntegrity(const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
@@ -342,6 +359,7 @@ private slots:
void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenYuzuFolder();
+ void OnVerifyInstalledContents();
void OnAbout();
void OnToggleFilterBar();
void OnToggleStatusBar();
@@ -356,6 +374,9 @@ private slots:
void ResetWindowSize720();
void ResetWindowSize900();
void ResetWindowSize1080();
+ void OnAlbum();
+ void OnCabinet(Service::NFP::CabinetMode mode);
+ void OnMiiEdit();
void OnCaptureScreenshot();
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
void OnLanguageChanged(const QString& locale);
@@ -374,9 +395,11 @@ private:
void RemoveVulkanDriverPipelineCache(u64 program_id);
void RemoveAllTransferableShaderCaches(u64 program_id);
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
+ void RemovePlayTimeData(u64 program_id);
void RemoveCacheStorage(u64 program_id);
- std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
- InstallResult InstallNSPXCI(const QString& filename);
+ bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
+ u64* selected_title_id, u8* selected_content_record_type);
+ InstallResult InstallNSP(const QString& filename);
InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
@@ -399,6 +422,8 @@ private:
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
bool CheckDarkMode();
bool CheckSystemArchiveDecryption();
+ bool CheckFirmwarePresence();
+ void ConfigureFilesystemProvider(const std::string& filepath);
QString GetTasStateDescription() const;
bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@@ -410,6 +435,7 @@ private:
std::unique_ptr<Core::System> system;
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+ std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
MultiplayerState* multiplayer_state = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 013ba0ceb..88684ffb5 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -137,6 +137,15 @@
<property name="title">
<string>&amp;Tools</string>
</property>
+ <widget class="QMenu" name="menu_cabinet_applet">
+ <property name="title">
+ <string>&amp;Amiibo</string>
+ </property>
+ <addaction name="action_Load_Cabinet_Nickname_Owner"/>
+ <addaction name="action_Load_Cabinet_Eraser"/>
+ <addaction name="action_Load_Cabinet_Restorer"/>
+ <addaction name="action_Load_Cabinet_Formatter"/>
+ </widget>
<widget class="QMenu" name="menuTAS">
<property name="title">
<string>&amp;TAS</string>
@@ -148,6 +157,11 @@
<addaction name="action_Configure_Tas"/>
</widget>
<addaction name="action_Rederive"/>
+ <addaction name="action_Verify_installed_contents"/>
+ <addaction name="separator"/>
+ <addaction name="menu_cabinet_applet"/>
+ <addaction name="action_Load_Album"/>
+ <addaction name="action_Load_Mii_Edit"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
<addaction name="menuTAS"/>
@@ -214,6 +228,11 @@
<string>&amp;Reinitialize keys...</string>
</property>
</action>
+ <action name="action_Verify_installed_contents">
+ <property name="text">
+ <string>&amp;Verify Installed Contents</string>
+ </property>
+ </action>
<action name="action_About">
<property name="text">
<string>&amp;About yuzu</string>
@@ -362,6 +381,36 @@
<string>&amp;Capture Screenshot</string>
</property>
</action>
+ <action name="action_Load_Album">
+ <property name="text">
+ <string>Open &amp;Album</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Nickname_Owner">
+ <property name="text">
+ <string>&amp;Set Nickname and Owner</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Eraser">
+ <property name="text">
+ <string>&amp;Delete Game Data</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Restorer">
+ <property name="text">
+ <string>&amp;Restore Amiibo</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Formatter">
+ <property name="text">
+ <string>&amp;Format Amiibo</string>
+ </property>
+ </action>
+ <action name="action_Load_Mii_Edit">
+ <property name="text">
+ <string>Open &amp;Mii Editor</string>
+ </property>
+ </action>
<action name="action_Configure_Tas">
<property name="text">
<string>&amp;Configure TAS...</string>
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index d71cc23a7..a415a953f 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -34,13 +34,14 @@ DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
connect(watcher, &QFutureWatcher<void>::finished, this, &DirectConnectWindow::OnConnection);
ui->nickname->setValidator(validation.GetNickname());
- ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
+ ui->nickname->setText(
+ QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue()));
if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
// Use yuzu Web Service user name as nickname by default
ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
}
ui->ip->setValidator(validation.GetIP());
- ui->ip->setText(UISettings::values.multiplayer_ip.GetValue());
+ ui->ip->setText(QString::fromStdString(UISettings::values.multiplayer_ip.GetValue()));
ui->port->setValidator(validation.GetPort());
ui->port->setText(QString::number(UISettings::values.multiplayer_port.GetValue()));
@@ -91,8 +92,8 @@ void DirectConnectWindow::Connect() {
}
// Store settings
- UISettings::values.multiplayer_nickname = ui->nickname->text();
- UISettings::values.multiplayer_ip = ui->ip->text();
+ UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString();
+ UISettings::values.multiplayer_ip = ui->ip->text().toStdString();
if (ui->port->isModified() && !ui->port->text().isEmpty()) {
UISettings::values.multiplayer_port = ui->port->text().toInt();
} else {
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index a8faa5b24..ef364ee43 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -55,12 +55,14 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host);
// Restore the settings:
- ui->username->setText(UISettings::values.multiplayer_room_nickname.GetValue());
+ ui->username->setText(
+ QString::fromStdString(UISettings::values.multiplayer_room_nickname.GetValue()));
if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) {
// Use yuzu Web Service user name as nickname by default
ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
}
- ui->room_name->setText(UISettings::values.multiplayer_room_name.GetValue());
+ ui->room_name->setText(
+ QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue()));
ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue()));
ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue());
int index = UISettings::values.multiplayer_host_type.GetValue();
@@ -72,7 +74,8 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
if (index != -1) {
ui->game_list->setCurrentIndex(index);
}
- ui->room_description->setText(UISettings::values.multiplayer_room_description.GetValue());
+ ui->room_description->setText(
+ QString::fromStdString(UISettings::values.multiplayer_room_description.GetValue()));
}
HostRoomWindow::~HostRoomWindow() = default;
@@ -218,8 +221,8 @@ void HostRoomWindow::Host() {
Network::NoPreferredIP, password, token);
// Store settings
- UISettings::values.multiplayer_room_nickname = ui->username->text();
- UISettings::values.multiplayer_room_name = ui->room_name->text();
+ UISettings::values.multiplayer_room_nickname = ui->username->text().toStdString();
+ UISettings::values.multiplayer_room_name = ui->room_name->text().toStdString();
UISettings::values.multiplayer_game_id =
ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
UISettings::values.multiplayer_max_player = ui->max_player->value();
@@ -230,7 +233,8 @@ void HostRoomWindow::Host() {
} else {
UISettings::values.multiplayer_room_port = Network::DefaultRoomPort;
}
- UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
+ UISettings::values.multiplayer_room_description =
+ ui->room_description->toPlainText().toStdString();
ui->host->setEnabled(true);
emit SaveConfig();
close();
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 387f6f7c9..603e9ae3d 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -60,7 +60,8 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
ui->room_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->nickname->setValidator(validation.GetNickname());
- ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
+ ui->nickname->setText(
+ QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue()));
// Try find the best nickname by default
if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) {
@@ -202,9 +203,9 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
// TODO(jroweboy): disable widgets and display a connecting while we wait
// Save settings
- UISettings::values.multiplayer_nickname = ui->nickname->text();
+ UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString();
UISettings::values.multiplayer_ip =
- proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
+ proxy->data(connection_index, LobbyItemHost::HostIPRole).value<QString>().toStdString();
UISettings::values.multiplayer_port =
proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
emit SaveConfig();
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp
new file mode 100644
index 000000000..155c36b7d
--- /dev/null
+++ b/src/yuzu/play_time_manager.cpp
@@ -0,0 +1,179 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/thread.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "yuzu/play_time_manager.h"
+
+namespace PlayTime {
+
+namespace {
+
+struct PlayTimeElement {
+ ProgramId program_id;
+ PlayTime play_time;
+};
+
+std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
+ const Service::Account::ProfileManager manager;
+ const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
+ if (!uuid.has_value()) {
+ return std::nullopt;
+ }
+ return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
+ uuid->RawString().append(".bin");
+}
+
+[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
+ const auto filename = GetCurrentUserPlayTimePath();
+
+ if (!filename.has_value()) {
+ LOG_ERROR(Frontend, "Failed to get current user path");
+ return false;
+ }
+
+ out_play_time_db.clear();
+
+ if (Common::FS::Exists(filename.value())) {
+ Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ if (!file.IsOpen()) {
+ LOG_ERROR(Frontend, "Failed to open play time file: {}",
+ Common::FS::PathToUTF8String(filename.value()));
+ return false;
+ }
+
+ const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
+ std::vector<PlayTimeElement> elements(num_elements);
+
+ if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
+ return false;
+ }
+
+ for (const auto& [program_id, play_time] : elements) {
+ if (program_id != 0) {
+ out_play_time_db[program_id] = play_time;
+ }
+ }
+ }
+
+ return true;
+}
+
+[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
+ const auto filename = GetCurrentUserPlayTimePath();
+
+ if (!filename.has_value()) {
+ LOG_ERROR(Frontend, "Failed to get current user path");
+ return false;
+ }
+
+ Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
+ Common::FS::FileType::BinaryFile};
+ if (!file.IsOpen()) {
+ LOG_ERROR(Frontend, "Failed to open play time file: {}",
+ Common::FS::PathToUTF8String(filename.value()));
+ return false;
+ }
+
+ std::vector<PlayTimeElement> elements;
+ elements.reserve(play_time_db.size());
+
+ for (auto& [program_id, play_time] : play_time_db) {
+ if (program_id != 0) {
+ elements.push_back(PlayTimeElement{program_id, play_time});
+ }
+ }
+
+ return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
+}
+
+} // namespace
+
+PlayTimeManager::PlayTimeManager() {
+ if (!ReadPlayTimeFile(database)) {
+ LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
+ }
+}
+
+PlayTimeManager::~PlayTimeManager() {
+ Save();
+}
+
+void PlayTimeManager::SetProgramId(u64 program_id) {
+ running_program_id = program_id;
+}
+
+void PlayTimeManager::Start() {
+ play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
+}
+
+void PlayTimeManager::Stop() {
+ play_time_thread = {};
+}
+
+void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("PlayTimeReport");
+
+ using namespace std::literals::chrono_literals;
+ using std::chrono::seconds;
+ using std::chrono::steady_clock;
+
+ auto timestamp = steady_clock::now();
+
+ const auto GetDuration = [&]() -> u64 {
+ const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
+ const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
+ return static_cast<u64>(duration.count());
+ };
+
+ while (!stop_token.stop_requested()) {
+ Common::StoppableTimedWait(stop_token, 30s);
+
+ database[running_program_id] += GetDuration();
+ Save();
+ }
+}
+
+void PlayTimeManager::Save() {
+ if (!WritePlayTimeFile(database)) {
+ LOG_ERROR(Frontend, "Failed to update play time database!");
+ }
+}
+
+u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
+ auto it = database.find(program_id);
+ if (it != database.end()) {
+ return it->second;
+ } else {
+ return 0;
+ }
+}
+
+void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
+ database.erase(program_id);
+ Save();
+}
+
+QString ReadablePlayTime(qulonglong time_seconds) {
+ if (time_seconds == 0) {
+ return {};
+ }
+ const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
+ const auto time_hours = static_cast<double>(time_seconds) / 3600;
+ const bool is_minutes = time_minutes < 60;
+ const char* unit = is_minutes ? "m" : "h";
+ const auto value = is_minutes ? time_minutes : time_hours;
+
+ return QStringLiteral("%L1 %2")
+ .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
+ .arg(QString::fromUtf8(unit));
+}
+
+} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h
new file mode 100644
index 000000000..5f96f3447
--- /dev/null
+++ b/src/yuzu/play_time_manager.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QString>
+
+#include <map>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/polyfill_thread.h"
+
+namespace PlayTime {
+
+using ProgramId = u64;
+using PlayTime = u64;
+using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
+
+class PlayTimeManager {
+public:
+ explicit PlayTimeManager();
+ ~PlayTimeManager();
+
+ YUZU_NON_COPYABLE(PlayTimeManager);
+ YUZU_NON_MOVEABLE(PlayTimeManager);
+
+ u64 GetPlayTime(u64 program_id) const;
+ void ResetProgramPlayTime(u64 program_id);
+ void SetProgramId(u64 program_id);
+ void Start();
+ void Stop();
+
+private:
+ PlayTimeDatabase database;
+ u64 running_program_id;
+ std::jthread play_time_thread;
+ void AutoTimestamp(std::stop_token stop_token);
+ void Save();
+};
+
+QString ReadablePlayTime(qulonglong time_seconds);
+
+} // namespace PlayTime
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 2c1b547fb..1c833767b 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -3,6 +3,18 @@
#include "yuzu/uisettings.h"
+#ifndef CANNOT_EXPLICITLY_INSTANTIATE
+namespace Settings {
+template class Setting<bool>;
+template class Setting<std::string>;
+template class Setting<u16, true>;
+template class Setting<u32>;
+template class Setting<u8, true>;
+template class Setting<u8>;
+template class Setting<unsigned long long>;
+} // namespace Settings
+#endif
+
namespace UISettings {
const Themes themes{{
@@ -24,4 +36,20 @@ bool IsDarkTheme() {
Values values = {};
+u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {
+ switch (ratio) {
+ case Settings::AspectRatio::R4_3:
+ return height * 4 / 3;
+ case Settings::AspectRatio::R21_9:
+ return height * 21 / 9;
+ case Settings::AspectRatio::R16_10:
+ return height * 16 / 10;
+ case Settings::AspectRatio::R16_9:
+ case Settings::AspectRatio::Stretch:
+ // TODO: Move this function wherever appropriate to implement Stretched aspect
+ break;
+ }
+ return height * 16 / 9;
+}
+
} // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 20a517d34..975008159 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -13,6 +13,22 @@
#include <QVector>
#include "common/common_types.h"
#include "common/settings.h"
+#include "common/settings_enums.h"
+
+using Settings::Category;
+using Settings::Setting;
+
+#ifndef CANNOT_EXPLICITLY_INSTANTIATE
+namespace Settings {
+extern template class Setting<bool>;
+extern template class Setting<std::string>;
+extern template class Setting<u16, true>;
+extern template class Setting<u32>;
+extern template class Setting<u8, true>;
+extern template class Setting<u8>;
+extern template class Setting<unsigned long long>;
+} // namespace Settings
+#endif
namespace UISettings {
@@ -56,6 +72,8 @@ struct GameDir {
};
struct Values {
+ Settings::Linkage linkage{1000};
+
QByteArray geometry;
QByteArray state;
@@ -64,30 +82,56 @@ struct Values {
QByteArray gamelist_header_state;
QByteArray microprofile_geometry;
- Settings::Setting<bool> microprofile_visible{false, "microProfileDialogVisible"};
-
- Settings::Setting<bool> single_window_mode{true, "singleWindowMode"};
- Settings::Setting<bool> fullscreen{false, "fullscreen"};
- Settings::Setting<bool> display_titlebar{true, "displayTitleBars"};
- Settings::Setting<bool> show_filter_bar{true, "showFilterBar"};
- Settings::Setting<bool> show_status_bar{true, "showStatusBar"};
-
- Settings::Setting<bool> confirm_before_closing{true, "confirmClose"};
- Settings::Setting<bool> first_start{true, "firstStart"};
- Settings::Setting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
- Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"};
- Settings::Setting<bool> hide_mouse{true, "hideInactiveMouse"};
- Settings::Setting<bool> controller_applet_disabled{false, "disableControllerApplet"};
-
+ Setting<bool> microprofile_visible{linkage, false, "microProfileDialogVisible",
+ Category::UiLayout};
+
+ Setting<bool> single_window_mode{linkage, true, "singleWindowMode", Category::Ui};
+ Setting<bool> fullscreen{linkage, false, "fullscreen", Category::Ui};
+ Setting<bool> display_titlebar{linkage, true, "displayTitleBars", Category::Ui};
+ Setting<bool> show_filter_bar{linkage, true, "showFilterBar", Category::Ui};
+ Setting<bool> show_status_bar{linkage, true, "showStatusBar", Category::Ui};
+
+ Setting<bool> confirm_before_closing{
+ linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
+ true, true};
+ Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
+ Setting<bool> pause_when_in_background{linkage,
+ false,
+ "pauseWhenInBackground",
+ Category::UiGeneral,
+ Settings::Specialization::Default,
+ true,
+ true};
+ Setting<bool> mute_when_in_background{
+ linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
+ true, true};
+ Setting<bool> hide_mouse{
+ linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
+ true, true};
+ Setting<bool> controller_applet_disabled{linkage, false, "disableControllerApplet",
+ Category::UiGeneral};
// Set when Vulkan is known to crash the application
bool has_broken_vulkan = false;
- Settings::Setting<bool> select_user_on_boot{false, "select_user_on_boot"};
+ Setting<bool> select_user_on_boot{linkage,
+ false,
+ "select_user_on_boot",
+ Category::UiGeneral,
+ Settings::Specialization::Default,
+ true,
+ true};
+ Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Ui};
// Discord RPC
- Settings::Setting<bool> enable_discord_presence{true, "enable_discord_presence"};
+ Setting<bool> enable_discord_presence{linkage, true, "enable_discord_presence", Category::Ui};
+
+ // logging
+ Setting<bool> show_console{linkage, false, "showConsole", Category::Ui};
- Settings::Setting<bool> enable_screenshot_save_as{true, "enable_screenshot_save_as"};
+ // Screenshots
+ Setting<bool> enable_screenshot_save_as{linkage, true, "enable_screenshot_save_as",
+ Category::Screenshots};
+ Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
QString roms_path;
QString symbols_path;
@@ -102,51 +146,55 @@ struct Values {
// Shortcut name <Shortcut, context>
std::vector<Shortcut> shortcuts;
- Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
+ Setting<u32> callout_flags{linkage, 0, "calloutFlags", Category::Ui};
// multiplayer settings
- Settings::Setting<QString> multiplayer_nickname{{}, "nickname"};
- Settings::Setting<QString> multiplayer_ip{{}, "ip"};
- Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
- Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};
- Settings::Setting<QString> multiplayer_room_name{{}, "room_name"};
- Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"};
- Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, UINT16_MAX,
- "room_port"};
- Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"};
- Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"};
- Settings::Setting<QString> multiplayer_room_description{{}, "room_description"};
+ Setting<std::string> multiplayer_nickname{linkage, {}, "nickname", Category::Multiplayer};
+ Setting<std::string> multiplayer_ip{linkage, {}, "ip", Category::Multiplayer};
+ Setting<u16, true> multiplayer_port{linkage, 24872, 0,
+ UINT16_MAX, "port", Category::Multiplayer};
+ Setting<std::string> multiplayer_room_nickname{
+ linkage, {}, "room_nickname", Category::Multiplayer};
+ Setting<std::string> multiplayer_room_name{linkage, {}, "room_name", Category::Multiplayer};
+ Setting<u8, true> multiplayer_max_player{linkage, 8, 0, 8, "max_player", Category::Multiplayer};
+ Setting<u16, true> multiplayer_room_port{linkage, 24872, 0,
+ UINT16_MAX, "room_port", Category::Multiplayer};
+ Setting<u8, true> multiplayer_host_type{linkage, 0, 0, 1, "host_type", Category::Multiplayer};
+ Setting<unsigned long long> multiplayer_game_id{linkage, {}, "game_id", Category::Multiplayer};
+ Setting<std::string> multiplayer_room_description{
+ linkage, {}, "room_description", Category::Multiplayer};
std::pair<std::vector<std::string>, std::vector<std::string>> multiplayer_ban_list;
- // logging
- Settings::Setting<bool> show_console{false, "showConsole"};
-
// Game List
- Settings::Setting<bool> show_add_ons{true, "show_add_ons"};
- Settings::Setting<uint32_t> game_icon_size{64, "game_icon_size"};
- Settings::Setting<uint32_t> folder_icon_size{48, "folder_icon_size"};
- Settings::Setting<uint8_t> row_1_text_id{3, "row_1_text_id"};
- Settings::Setting<uint8_t> row_2_text_id{2, "row_2_text_id"};
+ Setting<bool> show_add_ons{linkage, true, "show_add_ons", Category::UiGameList};
+ Setting<u32> game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList};
+ Setting<u32> folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList};
+ Setting<u8> row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList};
+ Setting<u8> row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList};
std::atomic_bool is_game_list_reload_pending{false};
- Settings::Setting<bool> cache_game_list{true, "cache_game_list"};
- Settings::Setting<bool> favorites_expanded{true, "favorites_expanded"};
+ Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
+ Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
QVector<u64> favorited_ids;
// Compatibility List
- Settings::Setting<bool> show_compat{false, "show_compat"};
+ Setting<bool> show_compat{linkage, false, "show_compat", Category::UiGameList};
// Size & File Types Column
- Settings::Setting<bool> show_size{true, "show_size"};
- Settings::Setting<bool> show_types{true, "show_types"};
+ Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
+ Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
+
+ // Play time
+ Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
bool configuration_applied;
bool reset_to_defaults;
bool shortcut_already_warned{false};
- Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};
};
extern Values values;
+u32 CalculateWidth(u32 height, Settings::AspectRatio ratio);
+
} // namespace UISettings
Q_DECLARE_METATYPE(UISettings::GameDir*);
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..61cf00176 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
#include <cmath>
#include <QPainter>
#include "yuzu/util/util.h"
+#ifdef _WIN32
+#include <windows.h>
+#include "common/fs/file.h"
+#endif
QFont GetMonospaceFont() {
QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
return circle_pixmap;
}
+
+bool SaveIconToFile(const std::string_view path, const QImage& image) {
+#if defined(WIN32)
+#pragma pack(push, 2)
+ struct IconDir {
+ WORD id_reserved;
+ WORD id_type;
+ WORD id_count;
+ };
+
+ struct IconDirEntry {
+ BYTE width;
+ BYTE height;
+ BYTE color_count;
+ BYTE reserved;
+ WORD planes;
+ WORD bit_count;
+ DWORD bytes_in_res;
+ DWORD image_offset;
+ };
+#pragma pack(pop)
+
+ QImage source_image = image.convertToFormat(QImage::Format_RGB32);
+ constexpr int bytes_per_pixel = 4;
+ const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
+
+ BITMAPINFOHEADER info_header{};
+ info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
+ info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
+ info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
+
+ const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
+ const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
+ .height = static_cast<BYTE>(source_image.height() * 2),
+ .color_count = 0,
+ .reserved = 0,
+ .planes = 1,
+ .bit_count = bytes_per_pixel * 8,
+ .bytes_in_res =
+ static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
+ .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
+
+ Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
+ Common::FS::FileType::BinaryFile);
+ if (!icon_file.IsOpen()) {
+ return false;
+ }
+
+ if (!icon_file.Write(icon_dir)) {
+ return false;
+ }
+ if (!icon_file.Write(icon_entry)) {
+ return false;
+ }
+ if (!icon_file.Write(info_header)) {
+ return false;
+ }
+
+ for (int y = 0; y < image.height(); y++) {
+ const auto* line = source_image.scanLine(source_image.height() - 1 - y);
+ std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
+ std::memcpy(line_data.data(), line, line_data.size());
+ if (!icon_file.Write(line_data)) {
+ return false;
+ }
+ }
+ icon_file.Close();
+
+ return true;
+#else
+ return false;
+#endif
+}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
#include <QString>
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
-QFont GetMonospaceFont();
+[[nodiscard]] QFont GetMonospaceFont();
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
-QString ReadableByteSize(qulonglong size);
+[[nodiscard]] QString ReadableByteSize(qulonglong size);
/**
* Creates a circle pixmap from a specified color
* @param color The color the pixmap shall have
* @return QPixmap circle pixmap
*/
-QPixmap CreateCirclePixmapFromColor(const QColor& color);
+[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
+
+/**
+ * Saves a windows icon to a file
+ * @param path The icons path
+ * @param image The image to save
+ * @return bool If the operation succeeded
+ */
+[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);
diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp
index e1a0e6a2a..92f10d315 100644
--- a/src/yuzu/vk_device_info.cpp
+++ b/src/yuzu/vk_device_info.cpp
@@ -3,6 +3,9 @@
#include <utility>
#include <vector>
+
+#include "yuzu/qt_common.h"
+
#include "common/dynamic_library.h"
#include "common/logging/log.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -11,7 +14,6 @@
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "vulkan/vulkan_core.h"
-#include "yuzu/qt_common.h"
#include "yuzu/vk_device_info.h"
class QWindow;