diff options
Diffstat (limited to 'src/yuzu')
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> - <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> - </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> - <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> - </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> - <div>This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.</div> - </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> - <div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div> - </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> - <div>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.</div> - </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> - <div>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.</div> - </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>&Tools</string> </property> + <widget class="QMenu" name="menu_cabinet_applet"> + <property name="title"> + <string>&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>&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>&Reinitialize keys...</string> </property> </action> + <action name="action_Verify_installed_contents"> + <property name="text"> + <string>&Verify Installed Contents</string> + </property> + </action> <action name="action_About"> <property name="text"> <string>&About yuzu</string> @@ -362,6 +381,36 @@ <string>&Capture Screenshot</string> </property> </action> + <action name="action_Load_Album"> + <property name="text"> + <string>Open &Album</string> + </property> + </action> + <action name="action_Load_Cabinet_Nickname_Owner"> + <property name="text"> + <string>&Set Nickname and Owner</string> + </property> + </action> + <action name="action_Load_Cabinet_Eraser"> + <property name="text"> + <string>&Delete Game Data</string> + </property> + </action> + <action name="action_Load_Cabinet_Restorer"> + <property name="text"> + <string>&Restore Amiibo</string> + </property> + </action> + <action name="action_Load_Cabinet_Formatter"> + <property name="text"> + <string>&Format Amiibo</string> + </property> + </action> + <action name="action_Load_Mii_Edit"> + <property name="text"> + <string>Open &Mii Editor</string> + </property> + </action> <action name="action_Configure_Tas"> <property name="text"> <string>&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; |