From 578d632e6fe7cdf4d4eb8f4bf9480ce0c7ffce68 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Wed, 1 Oct 2025 22:15:58 +0300 Subject: [PATCH 1/8] add setting, implement it on desktop --- src/citra_qt/citra_qt.cpp | 27 ++++--------- src/citra_qt/configuration/config.cpp | 55 +++++++++++++++++++++++++++ src/citra_qt/configuration/config.h | 8 ++++ src/common/settings.cpp | 1 + src/common/settings.h | 8 ++++ 5 files changed, 80 insertions(+), 19 deletions(-) diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 523c3a3be..a28a160b9 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -2680,25 +2680,14 @@ void GMainWindow::AdjustSpeedLimit(bool increase) { void GMainWindow::ToggleScreenLayout() { const Settings::LayoutOption new_layout = []() { - switch (Settings::values.layout_option.GetValue()) { - case Settings::LayoutOption::Default: - return Settings::LayoutOption::SingleScreen; - case Settings::LayoutOption::SingleScreen: - return Settings::LayoutOption::LargeScreen; - case Settings::LayoutOption::LargeScreen: - return Settings::LayoutOption::HybridScreen; - case Settings::LayoutOption::HybridScreen: - return Settings::LayoutOption::SideScreen; - case Settings::LayoutOption::SideScreen: - return Settings::LayoutOption::SeparateWindows; - case Settings::LayoutOption::SeparateWindows: - return Settings::LayoutOption::CustomLayout; - case Settings::LayoutOption::CustomLayout: - return Settings::LayoutOption::Default; - default: - LOG_ERROR(Frontend, "Unknown layout option {}", - Settings::values.layout_option.GetValue()); - return Settings::LayoutOption::Default; + const Settings::LayoutOption current_layout = Settings::values.layout_option.GetValue(); + const std::vector layouts_to_cycle = Settings::values.layouts_to_cycle.GetValue(); + const auto current_pos = distance(layouts_to_cycle.begin(),std::find(layouts_to_cycle.begin(),layouts_to_cycle.end(),current_layout)); + if (current_pos >= layouts_to_cycle.size() - 1) { + // either this layout wasn't found or it was last so move to the beginning + return layouts_to_cycle[0]; + }else { + return layouts_to_cycle[current_pos+1]; } }(); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 0e7777911..8a88244ce 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "citra_qt/configuration/config.h" #include "common/file_util.h" #include "common/settings.h" @@ -132,6 +134,34 @@ void QtConfig::ReadBasicSetting(Settings::Setting& setting) { setting.SetValue(qt_config->value(name, default_value).toString().toStdString()); } } +// definition for vectors of enums +template +void QtConfig::ReadBasicSetting(Settings::Setting, ranged>& setting) { + const QString name = QString::fromStdString(setting.GetLabel()); + const std::vector default_value = setting.GetDefault(); + QStringList stringList = qt_config->value(name).toStringList(); + + if (qt_config->value(name + QStringLiteral("/default"), false).toBool() || stringList.size() < 1) { + setting.SetValue(default_value); + } else { + if (stringList.size() < 1) { + setting.SetValue(default_value); + } else { + std::vector newValue; + for (const QString& str : stringList) { + if constexpr (std::is_enum_v) { + using TypeU = std::underlying_type_t; + newValue.push_back(static_cast(str.toInt())); + } else if constexpr (std::is_integral_v) { + newValue.push_back(str.toInt()); + } else { + newValue.push_back(str.toStdString()); + } + } + setting.SetValue(newValue); + } + } +} template void QtConfig::ReadBasicSetting(Settings::Setting& setting) { @@ -191,6 +221,29 @@ void QtConfig::WriteBasicSetting(const Settings::Setting& setting) qt_config->setValue(name, QString::fromStdString(value)); } +template +void QtConfig::WriteBasicSetting(const Settings::Setting, ranged>& setting) { + const QString name = QString::fromStdString(setting.GetLabel()); + const std::vector& value = setting.GetValue(); + + qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); + + QStringList stringList; + if constexpr (std::is_enum_v) { + // For enums, convert to underlying integer type strings + using TypeU = std::underlying_type_t; + for (const Type& item : value) { + stringList.append(QString::number(static_cast(item))); + } + } else { + // For non-enum types (assuming numeric) + for (const Type& item : value) { + stringList.append(QString::number(item)); + } + } + + qt_config->setValue(name, stringList); +} // Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable template <> void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { @@ -528,6 +581,7 @@ void QtConfig::ReadLayoutValues() { ReadGlobalSetting(Settings::values.small_screen_position); if (global) { + ReadBasicSetting(Settings::values.layouts_to_cycle); ReadBasicSetting(Settings::values.mono_render_option); ReadBasicSetting(Settings::values.custom_top_x); ReadBasicSetting(Settings::values.custom_top_y); @@ -1092,6 +1146,7 @@ void QtConfig::SaveLayoutValues() { WriteGlobalSetting(Settings::values.screen_gap); WriteGlobalSetting(Settings::values.small_screen_position); if (global) { + WriteBasicSetting(Settings::values.layouts_to_cycle); WriteBasicSetting(Settings::values.mono_render_option); WriteBasicSetting(Settings::values.custom_top_x); WriteBasicSetting(Settings::values.custom_top_y); diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index 2c9039dde..f343c17fc 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -120,6 +120,10 @@ private: template void ReadBasicSetting(Settings::Setting& setting); + // Add overload for vectors + template + void ReadBasicSetting(Settings::Setting, ranged>& setting); + /** Sets a value from the setting in the qt_config using the setting's label and default value. * * @param The setting @@ -127,6 +131,10 @@ private: template void WriteBasicSetting(const Settings::Setting& setting); + template + void WriteBasicSetting(const Settings::Setting, ranged>& setting); + + ConfigType type; std::unique_ptr qt_config; std::string qt_config_loc; diff --git a/src/common/settings.cpp b/src/common/settings.cpp index f91d1ec58..116dff25c 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -120,6 +120,7 @@ void LogSettings() { log_setting("Layout_ScreenGap", values.screen_gap.GetValue()); log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue()); log_setting("Layout_SmallScreenPosition", values.small_screen_position.GetValue()); + //log_setting("Layout_LayoutsToCycle",values.layouts_to_cycle.GetValue()); log_setting("Utility_DumpTextures", values.dump_textures.GetValue()); log_setting("Utility_CustomTextures", values.custom_textures.GetValue()); log_setting("Utility_PreloadTextures", values.preload_textures.GetValue()); diff --git a/src/common/settings.h b/src/common/settings.h index 77061db16..1f4fdfb96 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -522,6 +522,14 @@ struct Values { SwitchableSetting upright_screen{false, "upright_screen"}; SwitchableSetting secondary_display_layout{SecondaryDisplayLayout::None, "secondary_display_layout"}; + Setting> layouts_to_cycle{ + {LayoutOption::Default, LayoutOption::SingleScreen, LayoutOption::LargeScreen, + LayoutOption::SideScreen, +#ifndef ANDROID + LayoutOption::SeparateWindows, +#endif + LayoutOption::HybridScreen, LayoutOption::CustomLayout}, + "layouts_to_cycle"}; SwitchableSetting large_screen_proportion{4.f, 1.f, 16.f, "large_screen_proportion"}; SwitchableSetting screen_gap{0, "screen_gap"}; From 76ce6f02be42082a8c17907d3e468a097ddee9bf Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 2 Oct 2025 12:21:22 +0300 Subject: [PATCH 2/8] added qt ui --- .../configuration/configure_layout.ui | 88 +++++--- .../configuration/configure_layout_cycle.ui | 191 ++++++++++++++++++ 2 files changed, 247 insertions(+), 32 deletions(-) create mode 100644 src/citra_qt/configuration/configure_layout_cycle.ui diff --git a/src/citra_qt/configuration/configure_layout.ui b/src/citra_qt/configuration/configure_layout.ui index 1125ab2fb..fc4d149dc 100644 --- a/src/citra_qt/configuration/configure_layout.ui +++ b/src/citra_qt/configuration/configure_layout.ui @@ -6,8 +6,8 @@ 0 0 - 705 - 656 + 659 + 662 @@ -51,8 +51,8 @@ 0 0 - 688 - 799 + 646 + 824 @@ -130,18 +130,39 @@ - - - Swap screens - - - - - - - Rotate screens upright - - + + + + + + + Rotate screens upright + + + + + + + Swap screens + + + + + + + + + + 0 + 0 + + + + Customize layout cycling + + + + @@ -356,7 +377,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -376,7 +397,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -396,7 +417,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -416,7 +437,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -440,6 +461,12 @@ Bottom Screen + + false + + + false + @@ -451,7 +478,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -471,7 +498,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -491,7 +518,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -511,7 +538,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -538,7 +565,7 @@ - QAbstractSpinBox::ButtonSymbols::PlusMinus + QAbstractSpinBox::UpDownArrows 10 @@ -583,7 +610,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -610,7 +637,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -659,7 +686,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -672,7 +699,7 @@ - QAbstractSpinBox::ButtonSymbols::NoButtons + QAbstractSpinBox::UpDownArrows px @@ -717,9 +744,6 @@ - - Qt::Orientation::Vertical - 20 diff --git a/src/citra_qt/configuration/configure_layout_cycle.ui b/src/citra_qt/configuration/configure_layout_cycle.ui new file mode 100644 index 000000000..3e8a423ca --- /dev/null +++ b/src/citra_qt/configuration/configure_layout_cycle.ui @@ -0,0 +1,191 @@ + + + Dialog + + + + 0 + 0 + 398 + 297 + + + + + 0 + 0 + + + + Dialog + + + + + 10 + 10 + 381 + 281 + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 14 + + + + Screen Layout Cycling Customization + + + + + + + + 0 + 0 + + + + Select which screen layout options should be cycled with the "Toggle Screen Layout" hotkey + + + true + + + + + + + + + Default + + + true + + + + + + + Single Screen + + + true + + + + + + + Large Screen + + + true + + + + + + + Side by Side + + + true + + + + + + + Separate Windows + + + true + + + + + + + Hybrid + + + true + + + + + + + Custom + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From 5d3ba8af8394bb31e5a8471493c5f8d5122988fd Mon Sep 17 00:00:00 2001 From: David Griswold Date: Mon, 6 Oct 2025 11:41:36 +0300 Subject: [PATCH 3/8] QT UI for setting --- src/citra_qt/CMakeLists.txt | 3 + src/citra_qt/citra_qt.cpp | 16 +++-- .../configuration/configure_layout.cpp | 8 +++ .../configuration/configure_layout_cycle.cpp | 67 +++++++++++++++++++ .../configuration/configure_layout_cycle.h | 31 +++++++++ .../configuration/configure_layout_cycle.ui | 22 +++--- 6 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 src/citra_qt/configuration/configure_layout_cycle.cpp create mode 100644 src/citra_qt/configuration/configure_layout_cycle.h diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 4796fc922..8a1496ff7 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -49,6 +49,9 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL configuration/configure_layout.cpp configuration/configure_layout.h configuration/configure_layout.ui + configuration/configure_layout_cycle.cpp + configuration/configure_layout_cycle.h + configuration/configure_layout_cycle.ui configuration/configure_dialog.cpp configuration/configure_dialog.h configuration/configure_general.cpp diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index a28a160b9..8ac895dd7 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -2681,13 +2681,21 @@ void GMainWindow::AdjustSpeedLimit(bool increase) { void GMainWindow::ToggleScreenLayout() { const Settings::LayoutOption new_layout = []() { const Settings::LayoutOption current_layout = Settings::values.layout_option.GetValue(); - const std::vector layouts_to_cycle = Settings::values.layouts_to_cycle.GetValue(); - const auto current_pos = distance(layouts_to_cycle.begin(),std::find(layouts_to_cycle.begin(),layouts_to_cycle.end(),current_layout)); + std::vector layouts_to_cycle = + Settings::values.layouts_to_cycle.GetValue(); + const auto current_pos = + distance(layouts_to_cycle.begin(), + std::find(layouts_to_cycle.begin(), layouts_to_cycle.end(), current_layout)); + // if the layouts_to_cycle setting has somehow been + // cleared out, add just default back in + if (layouts_to_cycle.size() == 0) { + layouts_to_cycle.push_back(Settings::LayoutOption::Default); + } if (current_pos >= layouts_to_cycle.size() - 1) { // either this layout wasn't found or it was last so move to the beginning return layouts_to_cycle[0]; - }else { - return layouts_to_cycle[current_pos+1]; + } else { + return layouts_to_cycle[current_pos + 1]; } }(); diff --git a/src/citra_qt/configuration/configure_layout.cpp b/src/citra_qt/configuration/configure_layout.cpp index 58a0e66da..1cd06d937 100644 --- a/src/citra_qt/configuration/configure_layout.cpp +++ b/src/citra_qt/configuration/configure_layout.cpp @@ -6,6 +6,7 @@ #include #include "citra_qt/configuration/configuration_shared.h" #include "citra_qt/configuration/configure_layout.h" +#include "citra_qt/configuration/configure_layout_cycle.h" #include "common/settings.h" #include "ui_configure_layout.h" #ifdef ENABLE_OPENGL @@ -111,6 +112,13 @@ ConfigureLayout::ConfigureLayout(QWidget* parent) ui->bg_button->setIcon(color_icon); ui->bg_button->setEnabled(true); }); + + connect(ui->customize_layouts_to_cycle, &QPushButton::clicked, this, [this] { + ui->customize_layouts_to_cycle->setEnabled(false); + QDialog* layout_cycle_dialog = new ConfigureLayoutCycle(this); + layout_cycle_dialog->exec(); + ui->customize_layouts_to_cycle->setEnabled(true); + }); } ConfigureLayout::~ConfigureLayout() = default; diff --git a/src/citra_qt/configuration/configure_layout_cycle.cpp b/src/citra_qt/configuration/configure_layout_cycle.cpp new file mode 100644 index 000000000..3775410b4 --- /dev/null +++ b/src/citra_qt/configuration/configure_layout_cycle.cpp @@ -0,0 +1,67 @@ +#include +#include +#include "citra_qt/configuration/configure_layout_cycle.h" +#include "ui_configure_layout_cycle.h" + +ConfigureLayoutCycle::ConfigureLayoutCycle(QWidget* parent) + : QDialog(parent), ui(std::make_unique()) { + ui->setupUi(this); + SetConfiguration(); + ConnectEvents(); +} + +// You MUST define the destructor in the .cpp file +ConfigureLayoutCycle::~ConfigureLayoutCycle() = default; + +void ConfigureLayoutCycle::ConnectEvents() { + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &ConfigureLayoutCycle::ApplyConfiguration); +} + +void ConfigureLayoutCycle::SetConfiguration() { + for (auto option : Settings::values.layouts_to_cycle.GetValue()) { + switch (option) { + case Settings::LayoutOption::Default: + ui->defaultCheck->setChecked(true); + break; + case Settings::LayoutOption::SingleScreen: + ui->singleCheck->setChecked(true); + break; + case Settings::LayoutOption::LargeScreen: + ui->largeCheck->setChecked(true); + break; + case Settings::LayoutOption::SideScreen: + ui->sidebysideCheck->setChecked(true); + break; + case Settings::LayoutOption::SeparateWindows: + ui->separateCheck->setChecked(true); + break; + case Settings::LayoutOption::HybridScreen: + ui->hybridCheck->setChecked(true); + break; + case Settings::LayoutOption::CustomLayout: + ui->customCheck->setChecked(true); + break; + } + } +} + +void ConfigureLayoutCycle::ApplyConfiguration() { + std::vector newSetting{}; + if (ui->defaultCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::Default); + if (ui->singleCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::SingleScreen); + if (ui->sidebysideCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::SideScreen); + if (ui->largeCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::LargeScreen); + if (ui->separateCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::SeparateWindows); + if (ui->hybridCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::HybridScreen); + if (ui->customCheck->isChecked()) + newSetting.push_back(Settings::LayoutOption::CustomLayout); + Settings::values.layouts_to_cycle = newSetting; + accept(); +} diff --git a/src/citra_qt/configuration/configure_layout_cycle.h b/src/citra_qt/configuration/configure_layout_cycle.h new file mode 100644 index 000000000..2b9a8494f --- /dev/null +++ b/src/citra_qt/configuration/configure_layout_cycle.h @@ -0,0 +1,31 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#pragma once + +#include +#include +#include "common/settings.h" + +namespace Ui { +class ConfigureLayoutCycle; +} + +class ConfigureLayoutCycle : public QDialog { + Q_OBJECT + +public: + explicit ConfigureLayoutCycle(QWidget* parent = nullptr); + ~ConfigureLayoutCycle() override; + +public slots: + void ApplyConfiguration(); + +private slots: + +private: + void SetConfiguration(); + void ConnectEvents(); + + std::unique_ptr ui; +}; \ No newline at end of file diff --git a/src/citra_qt/configuration/configure_layout_cycle.ui b/src/citra_qt/configuration/configure_layout_cycle.ui index 3e8a423ca..cc180a1d1 100644 --- a/src/citra_qt/configuration/configure_layout_cycle.ui +++ b/src/citra_qt/configuration/configure_layout_cycle.ui @@ -1,7 +1,7 @@ - Dialog - + ConfigureLayoutCycle + 0 @@ -74,7 +74,7 @@ Default - true + false @@ -84,7 +84,7 @@ Single Screen - true + false @@ -94,7 +94,7 @@ Large Screen - true + false @@ -104,7 +104,7 @@ Side by Side - true + false @@ -114,7 +114,7 @@ Separate Windows - true + false @@ -124,7 +124,7 @@ Hybrid - true + false @@ -134,7 +134,7 @@ Custom - true + false @@ -158,7 +158,7 @@ buttonBox accepted() - Dialog + ConfigureLayoutCycle accept() @@ -174,7 +174,7 @@ buttonBox rejected() - Dialog + ConfigureLayoutCycle reject() From 427901ebc9a749e359353c1fe871e770d1884229 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Mon, 6 Oct 2025 15:38:05 +0300 Subject: [PATCH 4/8] clean up writeglobalsetting and readglobalsetting for less repeated code --- src/citra_qt/configuration/config.cpp | 76 +++++---------------------- src/citra_qt/configuration/config.h | 3 +- 2 files changed, 15 insertions(+), 64 deletions(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 8a88244ce..aeb59d47c 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -141,7 +141,8 @@ void QtConfig::ReadBasicSetting(Settings::Setting, ranged>& se const std::vector default_value = setting.GetDefault(); QStringList stringList = qt_config->value(name).toStringList(); - if (qt_config->value(name + QStringLiteral("/default"), false).toBool() || stringList.size() < 1) { + if (qt_config->value(name + QStringLiteral("/default"), false).toBool() || + stringList.size() < 1) { setting.SetValue(default_value); } else { if (stringList.size() < 1) { @@ -188,27 +189,7 @@ void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting& sett const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); setting.SetGlobal(use_global); if (global || !use_global) { - QVariant default_value{}; - if constexpr (std::is_enum_v) { - using TypeU = std::underlying_type_t; - default_value = QVariant::fromValue(static_cast(setting.GetDefault())); - setting.SetValue(static_cast(ReadSetting(name, default_value).value())); - } else { - default_value = QVariant::fromValue(setting.GetDefault()); - setting.SetValue(ReadSetting(name, default_value).value()); - } - } -} - -template <> -void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting& 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) { - const QString default_value = QString::fromStdString(setting.GetDefault()); - setting.SetValue( - ReadSetting(name, QVariant::fromValue(default_value)).toString().toStdString()); + ReadBasicSetting(setting); } } @@ -217,7 +198,8 @@ template <> void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const std::string& value = setting.GetValue(); - qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); + if (global) + qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); qt_config->setValue(name, QString::fromStdString(value)); } @@ -226,7 +208,8 @@ void QtConfig::WriteBasicSetting(const Settings::Setting, rang const QString name = QString::fromStdString(setting.GetLabel()); const std::vector& value = setting.GetValue(); - qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); + if (global) + qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); QStringList stringList; if constexpr (std::is_enum_v) { @@ -241,15 +224,14 @@ void QtConfig::WriteBasicSetting(const Settings::Setting, rang stringList.append(QString::number(item)); } } - - qt_config->setValue(name, stringList); } // Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable template <> void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const u16& value = setting.GetValue(); - qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); + if (global) + qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); qt_config->setValue(name, static_cast(value)); } @@ -257,7 +239,8 @@ template void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type value = setting.GetValue(); - qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); + if (global) + qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); if constexpr (std::is_enum_v) { qt_config->setValue(name, static_cast>(value)); } else { @@ -273,39 +256,7 @@ void QtConfig::WriteGlobalSetting(const Settings::SwitchableSettingsetValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); } if (global || !setting.UsingGlobal()) { - qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); - if constexpr (std::is_enum_v) { - qt_config->setValue(name, static_cast>(value)); - } else { - qt_config->setValue(name, QVariant::fromValue(value)); - } - } -} - -template <> -void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { - const QString name = QString::fromStdString(setting.GetLabel()); - const std::string& 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, QString::fromStdString(value)); - } -} - -// Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable -template <> -void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { - const QString name = QString::fromStdString(setting.GetLabel()); - const u16& 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, static_cast(value)); + WriteBasicSetting(setting); } } @@ -1473,7 +1424,8 @@ void QtConfig::WriteSetting(const QString& name, const QVariant& value) { void QtConfig::WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value) { - qt_config->setValue(name + QStringLiteral("/default"), value == default_value); + if (global) + qt_config->setValue(name + QStringLiteral("/default"), value == default_value); qt_config->setValue(name, value); } diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index f343c17fc..3fba498ed 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -131,10 +131,9 @@ private: template void WriteBasicSetting(const Settings::Setting& setting); - template + template void WriteBasicSetting(const Settings::Setting, ranged>& setting); - ConfigType type; std::unique_ptr qt_config; std::string qt_config_loc; From f43bc0fc2a4e7e2a6b0cb87522afc9a0c3c1598f Mon Sep 17 00:00:00 2001 From: David Griswold Date: Tue, 7 Oct 2025 21:27:50 +0300 Subject: [PATCH 5/8] QT ui working --- src/citra_qt/configuration/config.cpp | 5 +- .../configuration/configure_layout_cycle.cpp | 14 ++ .../configuration/configure_layout_cycle.h | 1 + .../configuration/configure_layout_cycle.ui | 172 ++++++++++-------- src/common/settings.cpp | 3 +- src/common/settings.h | 2 +- 6 files changed, 117 insertions(+), 80 deletions(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index aeb59d47c..9fdc8347c 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -530,9 +530,8 @@ void QtConfig::ReadLayoutValues() { ReadGlobalSetting(Settings::values.large_screen_proportion); ReadGlobalSetting(Settings::values.screen_gap); ReadGlobalSetting(Settings::values.small_screen_position); - + ReadGlobalSetting(Settings::values.layouts_to_cycle); if (global) { - ReadBasicSetting(Settings::values.layouts_to_cycle); ReadBasicSetting(Settings::values.mono_render_option); ReadBasicSetting(Settings::values.custom_top_x); ReadBasicSetting(Settings::values.custom_top_y); @@ -1096,8 +1095,8 @@ void QtConfig::SaveLayoutValues() { WriteGlobalSetting(Settings::values.large_screen_proportion); WriteGlobalSetting(Settings::values.screen_gap); WriteGlobalSetting(Settings::values.small_screen_position); + WriteGlobalSetting(Settings::values.layouts_to_cycle); if (global) { - WriteBasicSetting(Settings::values.layouts_to_cycle); WriteBasicSetting(Settings::values.mono_render_option); WriteBasicSetting(Settings::values.custom_top_x); WriteBasicSetting(Settings::values.custom_top_y); diff --git a/src/citra_qt/configuration/configure_layout_cycle.cpp b/src/citra_qt/configuration/configure_layout_cycle.cpp index 3775410b4..5a5a67e5a 100644 --- a/src/citra_qt/configuration/configure_layout_cycle.cpp +++ b/src/citra_qt/configuration/configure_layout_cycle.cpp @@ -16,9 +16,17 @@ ConfigureLayoutCycle::~ConfigureLayoutCycle() = default; void ConfigureLayoutCycle::ConnectEvents() { connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ConfigureLayoutCycle::ApplyConfiguration); + connect(ui->globalCheck, &QCheckBox::stateChanged, this, &ConfigureLayoutCycle::UpdateGlobal); } void ConfigureLayoutCycle::SetConfiguration() { + if (Settings::IsConfiguringGlobal()) { + ui->globalCheck->setChecked(true); + ui->globalCheck->setVisible(false); + } else { + ui->globalCheck->setChecked(Settings::values.layouts_to_cycle.UsingGlobal()); + ui->checkGroup->setDisabled(Settings::values.layouts_to_cycle.UsingGlobal()); + } for (auto option : Settings::values.layouts_to_cycle.GetValue()) { switch (option) { case Settings::LayoutOption::Default: @@ -65,3 +73,9 @@ void ConfigureLayoutCycle::ApplyConfiguration() { Settings::values.layouts_to_cycle = newSetting; accept(); } + +void ConfigureLayoutCycle::UpdateGlobal() { + Settings::values.layouts_to_cycle.SetGlobal(ui->globalCheck->isChecked()); + ui->checkGroup->setDisabled(ui->globalCheck->isChecked()); + ui->checkGroup->repaint(); // Force visual update +} diff --git a/src/citra_qt/configuration/configure_layout_cycle.h b/src/citra_qt/configuration/configure_layout_cycle.h index 2b9a8494f..3fa918d57 100644 --- a/src/citra_qt/configuration/configure_layout_cycle.h +++ b/src/citra_qt/configuration/configure_layout_cycle.h @@ -26,6 +26,7 @@ private slots: private: void SetConfiguration(); void ConnectEvents(); + void UpdateGlobal(); std::unique_ptr ui; }; \ No newline at end of file diff --git a/src/citra_qt/configuration/configure_layout_cycle.ui b/src/citra_qt/configuration/configure_layout_cycle.ui index cc180a1d1..b0b0ce13b 100644 --- a/src/citra_qt/configuration/configure_layout_cycle.ui +++ b/src/citra_qt/configuration/configure_layout_cycle.ui @@ -7,7 +7,7 @@ 0 0 398 - 297 + 310 @@ -25,10 +25,10 @@ 10 10 381 - 281 + 304 - + QLayout::SetDefaultConstraint @@ -67,78 +67,100 @@ - - - - - Default - - - false - - - - - - - Single Screen - - - false - - - - - - - Large Screen - - - false - - - - - - - Side by Side - - - false - - - - - - - Separate Windows - - - false - - - - - - - Hybrid - - - false - - - - - - - Custom - - - false - - - - + + + Use Global Value + + + true + + + + + + + true + + + + + + Qt::Horizontal + + + + + + + Default + + + false + + + + + + + Single Screen + + + false + + + + + + + Large Screen + + + false + + + + + + + Side by Side + + + false + + + + + + + Separate Windows + + + false + + + + + + + Hybrid + + + false + + + + + + + Custom + + + false + + + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 116dff25c..afb0c53fe 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -120,7 +120,7 @@ void LogSettings() { log_setting("Layout_ScreenGap", values.screen_gap.GetValue()); log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue()); log_setting("Layout_SmallScreenPosition", values.small_screen_position.GetValue()); - //log_setting("Layout_LayoutsToCycle",values.layouts_to_cycle.GetValue()); + // log_setting("Layout_LayoutsToCycle",values.layouts_to_cycle.GetValue()); log_setting("Utility_DumpTextures", values.dump_textures.GetValue()); log_setting("Utility_CustomTextures", values.custom_textures.GetValue()); log_setting("Utility_PreloadTextures", values.preload_textures.GetValue()); @@ -210,6 +210,7 @@ void RestoreGlobalState(bool is_powered_on) { values.layout_option.SetGlobal(true); values.portrait_layout_option.SetGlobal(true); values.secondary_display_layout.SetGlobal(true); + values.layouts_to_cycle.SetGlobal(true); values.swap_screen.SetGlobal(true); values.upright_screen.SetGlobal(true); values.large_screen_proportion.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 1f4fdfb96..18e4cf9b9 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -522,7 +522,7 @@ struct Values { SwitchableSetting upright_screen{false, "upright_screen"}; SwitchableSetting secondary_display_layout{SecondaryDisplayLayout::None, "secondary_display_layout"}; - Setting> layouts_to_cycle{ + SwitchableSetting> layouts_to_cycle{ {LayoutOption::Default, LayoutOption::SingleScreen, LayoutOption::LargeScreen, LayoutOption::SideScreen, #ifndef ANDROID From ac0c56f28cdaa3cf6a44b2f8576804d5d92aade4 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Wed, 15 Oct 2025 22:23:15 +0300 Subject: [PATCH 6/8] Added layouts to cycle setting, and the android setting classes to support it --- .../citra_emu/display/ScreenAdjustmentUtil.kt | 11 ++- .../settings/model/AbstractListSetting.kt | 9 ++ .../features/settings/model/IntListSetting.kt | 39 ++++++++ .../settings/model/view/MultiChoiceSetting.kt | 46 ++++++++++ .../settings/model/view/SettingsItem.kt | 1 + .../features/settings/ui/SettingsAdapter.kt | 88 +++++++++++++++++-- .../settings/ui/SettingsFragmentPresenter.kt | 14 +++ .../ui/viewholder/MultiChoiceViewHolder.kt | 80 +++++++++++++++++ .../features/settings/utils/SettingsFile.kt | 6 ++ .../app/src/main/res/values/strings.xml | 2 + 10 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt diff --git a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt index 105f49ab8..e63960fa8 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/display/ScreenAdjustmentUtil.kt @@ -12,6 +12,7 @@ import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.R import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.features.settings.model.IntListSetting import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.utils.EmulationMenuSettings @@ -31,8 +32,16 @@ class ScreenAdjustmentUtil( BooleanSetting.SWAP_SCREEN.boolean = isEnabled settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) } + fun cycleLayouts() { - val landscapeValues = context.resources.getIntArray(R.array.landscapeValues) + + val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list; + val landscapeValues = + if (landscapeLayoutsToCycle.isNotEmpty()) + landscapeLayoutsToCycle.toIntArray() + else context.resources.getIntArray( + R.array.landscapeValues + ) val portraitValues = context.resources.getIntArray(R.array.portraitValues) if (NativeLibrary.isPortraitMode) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt new file mode 100644 index 000000000..33d0b85cf --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/AbstractListSetting.kt @@ -0,0 +1,9 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.features.settings.model + +interface AbstractListSetting : AbstractSetting { + var list: List +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt new file mode 100644 index 000000000..9d78b5cdd --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntListSetting.kt @@ -0,0 +1,39 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.features.settings.model + +enum class IntListSetting( + override val key: String, + override val section: String, + override val defaultValue: List +) : AbstractListSetting { + LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0,1,2,3,4,5)); + + override var list: List = defaultValue + + override val valueAsString: String + get() = list.joinToString() + + + override val isRuntimeEditable: Boolean + get() { + for (setting in NOT_RUNTIME_EDITABLE) { + if (setting == this) { + return false + } + } + return true + } + + companion object { + private val NOT_RUNTIME_EDITABLE:List = listOf(); + + + fun from(key: String): IntListSetting? = + values().firstOrNull { it.key == key } + + fun clear() = values().forEach { it.list = it.defaultValue } + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt new file mode 100644 index 000000000..20786af7f --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/MultiChoiceSetting.kt @@ -0,0 +1,46 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.features.settings.model.view +import org.citra.citra_emu.features.settings.model.AbstractSetting +import org.citra.citra_emu.features.settings.model.IntListSetting +class MultiChoiceSetting( + setting: AbstractSetting?, + titleId: Int, + descriptionId: Int, + val choicesId: Int, + val valuesId: Int, + val key: String? = null, + val defaultValue: List? = null, + override var isEnabled: Boolean = true +) : SettingsItem(setting, titleId, descriptionId) { + override val type = TYPE_MULTI_CHOICE + + val selectedValues: List + get() { + if (setting == null) { + return defaultValue!! + } + try { + val setting = setting as IntListSetting + return setting.list + }catch (_: ClassCastException) { + } + return defaultValue!! + } + + /** + * Write a value to the backing list. If that int was previously null, + * initializes a new one and returns it, so it can be added to the Hashmap. + * + * @param selection New value of the int. + * @return the existing setting with the new value applied. + */ + fun setSelectedValue(selection: List): IntListSetting { + val intSetting = setting as IntListSetting + intSetting.list = selection + return intSetting + } + +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt index c3f11def5..68aa2226c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/view/SettingsItem.kt @@ -47,5 +47,6 @@ abstract class SettingsItem( const val TYPE_INPUT_BINDING = 8 const val TYPE_STRING_INPUT = 9 const val TYPE_FLOAT_INPUT = 10 + const val TYPE_MULTI_CHOICE = 11 } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt index bc55bd5d6..233637e12 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt @@ -41,12 +41,14 @@ import org.citra.citra_emu.features.settings.model.AbstractIntSetting import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.FloatSetting +import org.citra.citra_emu.features.settings.model.IntListSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.AbstractShortSetting import org.citra.citra_emu.features.settings.model.view.DateTimeSetting import org.citra.citra_emu.features.settings.model.view.InputBindingSetting import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting +import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting import org.citra.citra_emu.features.settings.model.view.SliderSetting import org.citra.citra_emu.features.settings.model.view.StringInputSetting import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting @@ -55,6 +57,7 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting import org.citra.citra_emu.features.settings.ui.viewholder.DateTimeViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.InputBindingSettingViewHolder +import org.citra.citra_emu.features.settings.ui.viewholder.MultiChoiceViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.RunnableViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder @@ -72,7 +75,8 @@ import kotlin.math.roundToInt class SettingsAdapter( private val fragmentView: SettingsFragmentView, public val context: Context -) : RecyclerView.Adapter(), DialogInterface.OnClickListener { +) : RecyclerView.Adapter(), DialogInterface.OnClickListener, + DialogInterface.OnMultiChoiceClickListener { private var settings: ArrayList? = null private var clickedItem: SettingsItem? = null private var clickedPosition: Int @@ -104,6 +108,10 @@ class SettingsAdapter( SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this) } + SettingsItem.TYPE_MULTI_CHOICE -> { + MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this) + } + SettingsItem.TYPE_SLIDER -> { SliderViewHolder(ListItemSettingBinding.inflate(inflater), this) } @@ -181,21 +189,30 @@ class SettingsAdapter( SettingsItem.TYPE_SLIDER -> { (oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled } + SettingsItem.TYPE_SWITCH -> { (oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled } + SettingsItem.TYPE_SINGLE_CHOICE -> { (oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled } + SettingsItem.TYPE_MULTI_CHOICE -> { + (oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled + } + SettingsItem.TYPE_DATETIME_SETTING -> { (oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled } + SettingsItem.TYPE_STRING_SINGLE_CHOICE -> { (oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled } + SettingsItem.TYPE_STRING_INPUT -> { (oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled } + else -> { oldItem == newItem } @@ -214,7 +231,7 @@ class SettingsAdapter( // If statement is required otherwise the app will crash on activity recreate ex. theme settings if (fragmentView.activityView != null) - // Reload the settings list to update the UI + // Reload the settings list to update the UI fragmentView.loadSettingsList() } @@ -232,6 +249,21 @@ class SettingsAdapter( onSingleChoiceClick(item) } + private fun onMultiChoiceClick(item: MultiChoiceSetting) { + clickedItem = item + + val value: BooleanArray = getSelectionForMultiChoiceValue(item); + dialog = MaterialAlertDialogBuilder(context) + .setTitle(item.nameId) + .setMultiChoiceItems(item.choicesId, value, this) + .show() + } + + fun onMultiChoiceClick(item: MultiChoiceSetting, position: Int) { + clickedPosition = position + onMultiChoiceClick(item) + } + private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { clickedItem = item dialog = context?.let { @@ -360,14 +392,14 @@ class SettingsAdapter( sliderString = sliderProgress.roundToInt().toString() if (textSliderValue?.text.toString() != sliderString) { textSliderValue?.setText(sliderString) - textSliderValue?.setSelection(textSliderValue?.length() ?: 0 ) + textSliderValue?.setSelection(textSliderValue?.length() ?: 0) } } else { val currentText = textSliderValue?.text.toString() val currentTextValue = currentText.toFloat() if (currentTextValue != sliderProgress) { textSliderValue?.setText(sliderString) - textSliderValue?.setSelection(textSliderValue?.length() ?: 0 ) + textSliderValue?.setSelection(textSliderValue?.length() ?: 0) } } } @@ -447,6 +479,7 @@ class SettingsAdapter( } it.setSelectedValue(value) } + is AbstractShortSetting -> { val value = getValueForSingleChoiceSelection(it, which).toShort() if (it.selectedValue.toShort() != value) { @@ -454,6 +487,7 @@ class SettingsAdapter( } it.setSelectedValue(value) } + else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!") } fragmentView?.putSetting(setting) @@ -499,11 +533,12 @@ class SettingsAdapter( val setting = it.setSelectedValue(value) fragmentView?.putSetting(setting) } + else -> { val setting = it.setSelectedValue(sliderProgress) fragmentView?.putSetting(setting) } - } + } fragmentView.loadSettingsList() closeDialog() } @@ -519,7 +554,7 @@ class SettingsAdapter( fragmentView?.putSetting(setting) fragmentView.loadSettingsList() closeDialog() - } + } } } clickedItem = null @@ -527,6 +562,21 @@ class SettingsAdapter( textInputValue = "" } + //onclick for multichoice + override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) { + val mcsetting = clickedItem as? MultiChoiceSetting + mcsetting?.let { + val value = getValueForMultiChoiceSelection(it, which) + if (it.selectedValues.contains(value) != isChecked) { + val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted()) + fragmentView?.putSetting(setting) + fragmentView?.onSettingChanged() + } + fragmentView.loadSettingsList() + } + } + + fun onLongClick(setting: AbstractSetting, position: Int): Boolean { MaterialAlertDialogBuilder(context) .setMessage(R.string.reset_setting_confirmation) @@ -616,6 +666,16 @@ class SettingsAdapter( } } + private fun getValueForMultiChoiceSelection(item: MultiChoiceSetting, which: Int): Int { + val valuesId = item.valuesId + return if (valuesId > 0) { + val valuesArray = context.resources.getIntArray(valuesId) + valuesArray[which] + } else { + which + } + } + private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { val value = item.selectedValue val valuesId = item.valuesId @@ -632,4 +692,20 @@ class SettingsAdapter( } return -1 } + + private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray { + val value = item.selectedValues; + val valuesId = item.valuesId; + if (valuesId > 0) { + val valuesArray = context.resources.getIntArray(valuesId); + val res = BooleanArray(valuesArray.size){false} + for (index in valuesArray.indices) { + if (value.contains(valuesArray[index])) { + res[index] = true; + } + } + return res; + } + return BooleanArray(1){false}; + } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index d4baf6166..b65622b03 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -14,6 +14,7 @@ import android.os.Build import android.text.TextUtils import androidx.preference.PreferenceManager import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.serialization.builtins.IntArraySerializer import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.R import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting @@ -24,12 +25,14 @@ import org.citra.citra_emu.features.settings.model.AbstractStringSetting import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.FloatSetting import org.citra.citra_emu.features.settings.model.IntSetting +import org.citra.citra_emu.features.settings.model.IntListSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.StringSetting import org.citra.citra_emu.features.settings.model.view.DateTimeSetting import org.citra.citra_emu.features.settings.model.view.HeaderSetting import org.citra.citra_emu.features.settings.model.view.InputBindingSetting +import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting import org.citra.citra_emu.features.settings.model.view.RunnableSetting import org.citra.citra_emu.features.settings.model.view.SettingsItem import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting @@ -1106,6 +1109,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) BooleanSetting.UPRIGHT_SCREEN.defaultValue ) ) + add( + MultiChoiceSetting( + IntListSetting.LAYOUTS_TO_CYCLE, + R.string.layouts_to_cycle, + R.string.layouts_to_cycle_description, + R.array.landscapeLayouts, + R.array.landscapeLayoutValues, + IntListSetting.LAYOUTS_TO_CYCLE.key, + IntListSetting.LAYOUTS_TO_CYCLE.defaultValue + ) + ) add( SingleChoiceSetting( IntSetting.PORTRAIT_SCREEN_LAYOUT, diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt new file mode 100644 index 000000000..8493115a4 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/viewholder/MultiChoiceViewHolder.kt @@ -0,0 +1,80 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.features.settings.ui.viewholder + +import android.view.View +import org.citra.citra_emu.databinding.ListItemSettingBinding +import org.citra.citra_emu.features.settings.model.view.SettingsItem +import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting +import org.citra.citra_emu.features.settings.ui.SettingsAdapter + +class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : + SettingViewHolder(binding.root, adapter) { + private lateinit var setting: SettingsItem + + override fun bind(item: SettingsItem) { + setting = item + binding.textSettingName.setText(item.nameId) + if (item.descriptionId != 0) { + binding.textSettingDescription.visibility = View.VISIBLE + binding.textSettingDescription.setText(item.descriptionId) + } else { + binding.textSettingDescription.visibility = View.GONE + } + binding.textSettingValue.visibility = View.VISIBLE + binding.textSettingValue.text = getTextSetting() + + if (setting.isActive) { + binding.textSettingName.alpha = 1f + binding.textSettingDescription.alpha = 1f + binding.textSettingValue.alpha = 1f + } else { + binding.textSettingName.alpha = 0.5f + binding.textSettingDescription.alpha = 0.5f + binding.textSettingValue.alpha = 0.5f + } + } + + private fun getTextSetting(): String { + when (val item = setting) { + is MultiChoiceSetting -> { + val resMgr = binding.textSettingDescription.context.resources + val values = resMgr.getIntArray(item.valuesId) + var resList:List = emptyList(); + values.forEachIndexed { i: Int, value: Int -> + if ((setting as MultiChoiceSetting).selectedValues.contains(value)) { + resList = resList + resMgr.getStringArray(item.choicesId)[i]; + } + } + return resList.joinToString(); + } + + else -> return "" + } + } + + override fun onClick(clicked: View) { + if (!setting.isEditable || !setting.isEnabled) { + adapter.onClickDisabledSetting(!setting.isEditable) + return + } + + if (setting is MultiChoiceSetting) { + adapter.onMultiChoiceClick( + (setting as MultiChoiceSetting), + bindingAdapterPosition + ) + } + } + + override fun onLongClick(clicked: View): Boolean { + if (setting.isActive) { + return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) + } else { + adapter.onClickDisabledSetting(!setting.isEditable) + } + return false + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt index dec3e4e0a..6c3cd8265 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/utils/SettingsFile.kt @@ -12,6 +12,7 @@ import org.citra.citra_emu.R import org.citra.citra_emu.features.settings.model.AbstractSetting import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.FloatSetting +import org.citra.citra_emu.features.settings.model.IntListSetting import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.ScaledFloatSetting import org.citra.citra_emu.features.settings.model.SettingSection @@ -255,6 +256,11 @@ object SettingsFile { return stringSetting } + val intListSetting = IntListSetting.from(key) + if (intListSetting != null) { + intListSetting.list = value.split(", ").map { it.toInt() } + } + return null } diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 078113f12..975a8708b 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -341,6 +341,8 @@ Reverse Landscape Portrait Reverse Portrait + Layouts to Cycle + Which layouts are cycled by the Cycle Layout hotkey Default 16:9 4:3 From 6ffe17c63832272000585f4f0f3ebf5518d6fa08 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 16 Oct 2025 08:55:22 +0300 Subject: [PATCH 7/8] remove unused var in config.cpp --- src/citra_qt/configuration/config.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 9fdc8347c..817a731bc 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -251,7 +251,6 @@ void QtConfig::WriteBasicSetting(const Settings::Setting& setting) template void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting& 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()); } From e8debf292d34eeb0b7b096b72ff5a77d13fd21c4 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Thu, 16 Oct 2025 14:27:43 +0300 Subject: [PATCH 8/8] fix qt bugs --- src/citra_qt/configuration/config.cpp | 1 + src/citra_qt/configuration/configure_layout_cycle.ui | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 817a731bc..a1b6f59e5 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -224,6 +224,7 @@ void QtConfig::WriteBasicSetting(const Settings::Setting, rang stringList.append(QString::number(item)); } } + qt_config->setValue(name, stringList); } // Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable template <> diff --git a/src/citra_qt/configuration/configure_layout_cycle.ui b/src/citra_qt/configuration/configure_layout_cycle.ui index b0b0ce13b..e1d6aef4f 100644 --- a/src/citra_qt/configuration/configure_layout_cycle.ui +++ b/src/citra_qt/configuration/configure_layout_cycle.ui @@ -2,12 +2,15 @@ ConfigureLayoutCycle + + Qt::ApplicationModal + 0 0 - 398 - 310 + 395 + 334 @@ -17,7 +20,7 @@ - Dialog + Configure Layout Cycling @@ -25,7 +28,7 @@ 10 10 381 - 304 + 323