diff --git a/src/citron/configuration/configure.ui b/src/citron/configuration/configure.ui index 7d1558662..406e450e4 100644 --- a/src/citron/configuration/configure.ui +++ b/src/citron/configuration/configure.ui @@ -270,11 +270,6 @@ background-color: transparent; } - QComboBox::down-arrow { - width: 12px; - height: 12px; - background-color: #ffffff; - } QComboBox QAbstractItemView { background-color: #3d3d3d; @@ -512,8 +507,8 @@ height: 20px; } } - - + + 8 diff --git a/src/citron/configuration/configure_audio.cpp b/src/citron/configuration/configure_audio.cpp index a58031179..d28f6a7bf 100644 --- a/src/citron/configuration/configure_audio.cpp +++ b/src/citron/configuration/configure_audio.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -18,12 +19,13 @@ #include "citron/configuration/configure_audio.h" #include "citron/configuration/shared_translation.h" #include "citron/configuration/shared_widget.h" +#include "citron/theme.h" // Added for theming #include "citron/uisettings.h" ConfigureAudio::ConfigureAudio(const Core::System& system_, std::shared_ptr> group_, const ConfigurationShared::Builder& builder, QWidget* parent) - : Tab(group_, parent), ui(std::make_unique()), system{system_} { +: Tab(group_, parent), ui(std::make_unique()), system{system_} { ui->setupUi(this); Setup(builder); @@ -70,7 +72,7 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) { auto global_sink_match = [this] { return static_cast(sink_combo_box->currentIndex()) == - Settings::values.sink_id.GetValue(true); + Settings::values.sink_id.GetValue(true); }; if (setting->Id() == Settings::values.sink_id.Id()) { // TODO (lat9nq): Let the system manage sink_id @@ -117,8 +119,8 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) { if (!Settings::IsConfiguringGlobal()) { restore_output_device_button = - ConfigurationShared::Widget::CreateRestoreGlobalButton( - Settings::values.audio_output_device_id.UsingGlobal(), widget); + ConfigurationShared::Widget::CreateRestoreGlobalButton( + Settings::values.audio_output_device_id.UsingGlobal(), widget); restore_output_device_button->setEnabled(global_sink_match()); restore_output_device_button->setVisible( !Settings::values.audio_output_device_id.UsingGlobal()); @@ -143,8 +145,8 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) { if (!Settings::IsConfiguringGlobal()) { restore_input_device_button = - ConfigurationShared::Widget::CreateRestoreGlobalButton( - Settings::values.audio_input_device_id.UsingGlobal(), widget); + ConfigurationShared::Widget::CreateRestoreGlobalButton( + Settings::values.audio_input_device_id.UsingGlobal(), widget); widget->layout()->addWidget(restore_input_device_button); connect(restore_input_device_button, &QAbstractButton::clicked, [this](bool) { Settings::values.audio_input_device_id.SetGlobal(true); @@ -198,7 +200,7 @@ void ConfigureAudio::SetOutputDevicesFromDeviceID() { int new_device_index = 0; const QString output_device_id = - QString::fromStdString(Settings::values.audio_output_device_id.GetValue()); + QString::fromStdString(Settings::values.audio_output_device_id.GetValue()); 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; @@ -212,7 +214,7 @@ void ConfigureAudio::SetOutputDevicesFromDeviceID() { void ConfigureAudio::SetInputDevicesFromDeviceID() { int new_device_index = 0; const QString input_device_id = - QString::fromStdString(Settings::values.audio_input_device_id.GetValue()); + QString::fromStdString(Settings::values.audio_input_device_id.GetValue()); 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; @@ -251,7 +253,7 @@ void ConfigureAudio::UpdateAudioDevices(int sink_index) { output_device_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); const auto sink_id = - Settings::ToEnum(sink_combo_box->itemText(sink_index).toStdString()); + Settings::ToEnum(sink_combo_box->itemText(sink_index).toStdString()); for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) { output_device_combo_box->addItem(QString::fromStdString(device)); } @@ -276,3 +278,16 @@ void ConfigureAudio::InitializeAudioSinkComboBox() { void ConfigureAudio::RetranslateUI() { ui->retranslateUi(this); } + +QString ConfigureAudio::GetTemplateStyleSheet() const { + return m_template_style_sheet; +} + +void ConfigureAudio::SetTemplateStyleSheet(const QString& sheet) { + if (m_template_style_sheet == sheet) { + return; + } + m_template_style_sheet = sheet; + setStyleSheet(sheet); + emit TemplateStyleSheetChanged(); +} diff --git a/src/citron/configuration/configure_audio.h b/src/citron/configuration/configure_audio.h index 6cbcafb1b..08840eeac 100644 --- a/src/citron/configuration/configure_audio.h +++ b/src/citron/configuration/configure_audio.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -6,26 +7,30 @@ #include #include #include +#include // Added for stylesheet property #include #include "citron/configuration/configuration_shared.h" class QComboBox; namespace Core { -class System; + class System; } namespace Ui { -class ConfigureAudio; + class ConfigureAudio; } namespace ConfigurationShared { -class Builder; + class Builder; } class ConfigureAudio : public ConfigurationShared::Tab { Q_OBJECT + // This property allows the main UI file to pass its stylesheet to this widget + Q_PROPERTY(QString templateStyleSheet READ GetTemplateStyleSheet WRITE SetTemplateStyleSheet NOTIFY TemplateStyleSheetChanged) + public: explicit ConfigureAudio(const Core::System& system_, std::shared_ptr> group, @@ -35,6 +40,13 @@ public: void ApplyConfiguration() override; void SetConfiguration() override; + // These functions get and set the stylesheet property + QString GetTemplateStyleSheet() const; + void SetTemplateStyleSheet(const QString& sheet); + +signals: + void TemplateStyleSheetChanged(); + private: void changeEvent(QEvent* event) override; @@ -63,4 +75,7 @@ private: QPushButton* restore_output_device_button; QComboBox* input_device_combo_box; QPushButton* restore_input_device_button; + + // This variable will hold the raw stylesheet string + QString m_template_style_sheet; }; diff --git a/src/citron/configuration/configure_cpu.cpp b/src/citron/configuration/configure_cpu.cpp index 04f322845..9fb0c9059 100644 --- a/src/citron/configuration/configure_cpu.cpp +++ b/src/citron/configuration/configure_cpu.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "citron/configuration/configure_cpu.h" #include #include #include @@ -12,13 +14,12 @@ #include "core/core.h" #include "ui_configure_cpu.h" #include "citron/configuration/configuration_shared.h" -#include "citron/configuration/configure_cpu.h" ConfigureCpu::ConfigureCpu(const Core::System& system_, std::shared_ptr> group_, const ConfigurationShared::Builder& builder, QWidget* parent) - : Tab(group_, parent), ui{std::make_unique()}, system{system_}, - combobox_translations(builder.ComboboxTranslations()) { +: Tab(group_, parent), ui{std::make_unique()}, system{system_}, +combobox_translations(builder.ComboboxTranslations()) { ui->setupUi(this); Setup(builder); @@ -31,9 +32,9 @@ ConfigureCpu::ConfigureCpu(const Core::System& system_, connect(backend_combobox, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureCpu::UpdateGroup); -#ifdef HAS_NCE + #ifdef HAS_NCE ui->backend_group->setVisible(true); -#endif + #endif } ConfigureCpu::~ConfigureCpu() = default; @@ -90,7 +91,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { void ConfigureCpu::UpdateGroup(int index) { const auto accuracy = static_cast( combobox_translations.at(Settings::EnumMetadata::Index())[index] - .first); + .first); ui->unsafe_group->setVisible(accuracy == Settings::CpuAccuracy::Unsafe); } @@ -112,3 +113,16 @@ void ConfigureCpu::changeEvent(QEvent* event) { void ConfigureCpu::RetranslateUI() { ui->retranslateUi(this); } + +QString ConfigureCpu::GetTemplateStyleSheet() const { + return m_template_style_sheet; +} + +void ConfigureCpu::SetTemplateStyleSheet(const QString& sheet) { + if (m_template_style_sheet == sheet) { + return; + } + m_template_style_sheet = sheet; + setStyleSheet(sheet); + emit TemplateStyleSheetChanged(); +} diff --git a/src/citron/configuration/configure_cpu.h b/src/citron/configuration/configure_cpu.h index 2079cedcb..991e5d7b0 100644 --- a/src/citron/configuration/configure_cpu.h +++ b/src/citron/configuration/configure_cpu.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include +#include // Added for stylesheet property #include #include "citron/configuration/configuration_shared.h" #include "citron/configuration/shared_translation.h" @@ -12,20 +14,23 @@ class QComboBox; namespace Core { -class System; + class System; } namespace Ui { -class ConfigureCpu; + class ConfigureCpu; } namespace ConfigurationShared { -class Builder; + class Builder; } class ConfigureCpu : public ConfigurationShared::Tab { Q_OBJECT + // This property allows the main UI file to pass its stylesheet to this widget + Q_PROPERTY(QString templateStyleSheet READ GetTemplateStyleSheet WRITE SetTemplateStyleSheet NOTIFY TemplateStyleSheetChanged) + public: explicit ConfigureCpu(const Core::System& system_, std::shared_ptr> group, @@ -35,6 +40,13 @@ public: void ApplyConfiguration() override; void SetConfiguration() override; + // These functions get and set the stylesheet property + QString GetTemplateStyleSheet() const; + void SetTemplateStyleSheet(const QString& sheet); + +signals: + void TemplateStyleSheetChanged(); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); @@ -52,4 +64,7 @@ private: QComboBox* accuracy_combobox; QComboBox* backend_combobox; + + // This variable will hold the raw stylesheet string + QString m_template_style_sheet; }; diff --git a/src/citron/configuration/configure_dialog.cpp b/src/citron/configuration/configure_dialog.cpp index 85f4d66cd..8c43fa45b 100644 --- a/src/citron/configuration/configure_dialog.cpp +++ b/src/citron/configuration/configure_dialog.cpp @@ -18,7 +18,7 @@ #include "core/core.h" #include "ui_configure.h" #include "vk_device_info.h" -#include "citron/configuration/configuration_shared.h" // <-- Full definition included here +#include "citron/configuration/configuration_shared.h" #include "citron/configuration/configure_applets.h" #include "citron/configuration/configure_audio.h" #include "citron/configuration/configure_cpu.h" @@ -173,7 +173,6 @@ rainbow_timer{new QTimer(this)} { ui->buttonBox->setFocus(); } -// This line defines the destructor, completing the type for std::unique_ptr ConfigureDialog::~ConfigureDialog() = default; void ConfigureDialog::UpdateTheme() { @@ -185,7 +184,8 @@ void ConfigureDialog::UpdateTheme() { } accent_color_str = QColor::fromHsvF(rainbow_hue, 0.8, 1.0).name(); if (!rainbow_timer->isActive()) { - rainbow_timer->start(16); // ~60 FPS + // THE FIX: Use a sane timer interval to prevent UI lag. + rainbow_timer->start(100); } } else { if (rainbow_timer->isActive()) { @@ -203,6 +203,13 @@ void ConfigureDialog::UpdateTheme() { style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover); style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed); setStyleSheet(style_sheet); + + // Pass the processed stylesheet to all child tabs that need it. + graphics_tab->SetTemplateStyleSheet(style_sheet); + system_tab->SetTemplateStyleSheet(style_sheet); + audio_tab->SetTemplateStyleSheet(style_sheet); + cpu_tab->SetTemplateStyleSheet(style_sheet); + graphics_advanced_tab->SetTemplateStyleSheet(style_sheet); } void ConfigureDialog::SetConfiguration() {} diff --git a/src/citron/configuration/configure_graphics.cpp b/src/citron/configuration/configure_graphics.cpp index f3910ebaf..42ef5de66 100644 --- a/src/citron/configuration/configure_graphics.cpp +++ b/src/citron/configuration/configure_graphics.cpp @@ -591,3 +591,35 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { } return selected_backend; } + +QString ConfigureGraphics::GetTemplateStyleSheet() const { + return m_template_style_sheet; +} + +void ConfigureGraphics::SetTemplateStyleSheet(const QString& sheet) { + if (m_template_style_sheet == sheet) { + return; + } + + m_template_style_sheet = sheet; + + // Get the current accent color from the global UI settings + const QColor accent_color(QString::fromStdString(UISettings::values.accent_color.GetValue())); + + // Create color variations for hover and pressed states + const QColor accent_color_hover = accent_color.lighter(115); + const QColor accent_color_pressed = accent_color.darker(115); + + QString final_style = m_template_style_sheet; + + // Replace all placeholders with the actual color values in #RRGGBB format + // Use QStringLiteral() to satisfy Qt 6's explicit string constructor requirements + final_style.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color.name(QColor::HexRgb)); + final_style.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover.name(QColor::HexRgb)); + final_style.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed.name(QColor::HexRgb)); + + // Apply the processed stylesheet to this widget and all its children (like checkboxes) + this->setStyleSheet(final_style); + + emit TemplateStyleSheetChanged(); +} diff --git a/src/citron/configuration/configure_graphics.h b/src/citron/configuration/configure_graphics.h index 33ae92031..cb519fb3a 100644 --- a/src/citron/configuration/configure_graphics.h +++ b/src/citron/configuration/configure_graphics.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2016 Citra Emulator Project // SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -46,6 +46,9 @@ class Builder; class ConfigureGraphics : public ConfigurationShared::Tab { Q_OBJECT + // This property allows the main UI file to pass its stylesheet to this widget + Q_PROPERTY(QString templateStyleSheet READ GetTemplateStyleSheet WRITE SetTemplateStyleSheet NOTIFY TemplateStyleSheetChanged) + public: explicit ConfigureGraphics( const Core::System& system_, std::vector& records, @@ -59,6 +62,13 @@ public: void ApplyConfiguration() override; void SetConfiguration() override; + // These functions get and set the stylesheet property + QString GetTemplateStyleSheet() const; + void SetTemplateStyleSheet(const QString& sheet); + +signals: + void TemplateStyleSheetChanged(); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); @@ -115,4 +125,7 @@ private: QComboBox* aspect_ratio_combobox; QComboBox* resolution_combobox; QWidget* fsr_sharpness_widget; + + // This variable will hold the raw stylesheet string + QString m_template_style_sheet; }; diff --git a/src/citron/configuration/configure_graphics_advanced.cpp b/src/citron/configuration/configure_graphics_advanced.cpp index cf1864a6f..deb20b2bf 100644 --- a/src/citron/configuration/configure_graphics_advanced.cpp +++ b/src/citron/configuration/configure_graphics_advanced.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "citron/configuration/configure_graphics_advanced.h" #include #include #include @@ -8,14 +10,13 @@ #include "core/core.h" #include "ui_configure_graphics_advanced.h" #include "citron/configuration/configuration_shared.h" -#include "citron/configuration/configure_graphics_advanced.h" #include "citron/configuration/shared_translation.h" #include "citron/configuration/shared_widget.h" ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced( const Core::System& system_, std::shared_ptr> group_, const ConfigurationShared::Builder& builder, QWidget* parent) - : Tab(group_, parent), ui{std::make_unique()}, system{system_} { +: Tab(group_, parent), ui{std::make_unique()}, system{system_} { ui->setupUi(this); @@ -35,27 +36,27 @@ void ConfigureGraphicsAdvanced::Setup(const ConfigurationShared::Builder& builde std::map hold{}; // A map will sort the data for us for (auto setting : - Settings::values.linkage.by_category[Settings::Category::RendererAdvanced]) { + 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; - } + if (widget == nullptr) { + continue; } - for (const auto& [id, widget] : hold) { - layout.addWidget(widget); + 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() { @@ -80,3 +81,16 @@ void ConfigureGraphicsAdvanced::RetranslateUI() { void ConfigureGraphicsAdvanced::ExposeComputeOption() { checkbox_enable_compute_pipelines->setVisible(true); } + +QString ConfigureGraphicsAdvanced::GetTemplateStyleSheet() const { + return m_template_style_sheet; +} + +void ConfigureGraphicsAdvanced::SetTemplateStyleSheet(const QString& sheet) { + if (m_template_style_sheet == sheet) { + return; + } + m_template_style_sheet = sheet; + setStyleSheet(sheet); + emit TemplateStyleSheetChanged(); +} diff --git a/src/citron/configuration/configure_graphics_advanced.h b/src/citron/configuration/configure_graphics_advanced.h index 385e89a5a..4951f2d02 100644 --- a/src/citron/configuration/configure_graphics_advanced.h +++ b/src/citron/configuration/configure_graphics_advanced.h @@ -1,28 +1,33 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include +#include // Added for stylesheet property #include #include "citron/configuration/configuration_shared.h" namespace Core { -class System; + class System; } namespace Ui { -class ConfigureGraphicsAdvanced; + class ConfigureGraphicsAdvanced; } namespace ConfigurationShared { -class Builder; + class Builder; } class ConfigureGraphicsAdvanced : public ConfigurationShared::Tab { Q_OBJECT + // This property allows the main UI file to pass its stylesheet to this widget + Q_PROPERTY(QString templateStyleSheet READ GetTemplateStyleSheet WRITE SetTemplateStyleSheet NOTIFY TemplateStyleSheetChanged) + public: explicit ConfigureGraphicsAdvanced( const Core::System& system_, std::shared_ptr> group, @@ -34,6 +39,13 @@ public: void ExposeComputeOption(); + // These functions get and set the stylesheet property + QString GetTemplateStyleSheet() const; + void SetTemplateStyleSheet(const QString& sheet); + +signals: + void TemplateStyleSheetChanged(); + private: void Setup(const ConfigurationShared::Builder& builder); void changeEvent(QEvent* event) override; @@ -46,4 +58,7 @@ private: std::vector> apply_funcs; QWidget* checkbox_enable_compute_pipelines{}; + + // This variable will hold the raw stylesheet string + QString m_template_style_sheet; }; diff --git a/src/citron/configuration/configure_per_game.cpp b/src/citron/configuration/configure_per_game.cpp index f6717fbbf..2d25d6d84 100644 --- a/src/citron/configuration/configure_per_game.cpp +++ b/src/citron/configuration/configure_per_game.cpp @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "citron/configuration/configure_per_game.h" #include #include #include @@ -14,9 +15,11 @@ #include #include +#include #include #include #include +#include #include #include "common/fs/fs_util.h" @@ -42,9 +45,9 @@ #include "citron/configuration/configure_graphics_advanced.h" #include "citron/configuration/configure_input_per_game.h" #include "citron/configuration/configure_linux_tab.h" -#include "citron/configuration/configure_per_game.h" #include "citron/configuration/configure_per_game_addons.h" #include "citron/configuration/configure_system.h" +#include "citron/theme.h" #include "citron/uisettings.h" #include "citron/util/util.h" #include "citron/vk_device_info.h" @@ -55,7 +58,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st : QDialog(parent), ui(std::make_unique()), title_id{title_id_}, system{system_}, builder{std::make_unique(this, !system_.IsPoweredOn())}, -tab_group{std::make_shared>()} { +tab_group{std::make_shared>()} , +rainbow_timer{new QTimer(this)} { 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); @@ -74,14 +78,16 @@ tab_group{std::make_shared>()} { ui->setupUi(this); - // THIS IS THE NEW FIX: Force a minimum height on the window. + ApplyStaticTheme(); + UpdateTheme(); // Run once to set initial colors + connect(rainbow_timer, &QTimer::timeout, this, &ConfigurePerGame::UpdateTheme); + setMinimumHeight(400); layout()->setSizeConstraint(QLayout::SetDefaultConstraint); ui->tabWidget->addTab(addons_tab.get(), tr("Add-Ons")); - // Create a scroll area for the system tab QScrollArea* system_scroll_area = new QScrollArea(this); system_scroll_area->setWidgetResizable(true); system_scroll_area->setWidget(system_tab.get()); @@ -89,13 +95,11 @@ tab_group{std::make_shared>()} { ui->tabWidget->addTab(cpu_tab.get(), tr("CPU")); - // Create a scroll area for the graphics tab QScrollArea* graphics_scroll_area = new QScrollArea(this); graphics_scroll_area->setWidgetResizable(true); graphics_scroll_area->setWidget(graphics_tab.get()); ui->tabWidget->addTab(graphics_scroll_area, tr("Graphics")); - // Create a scroll area for the advanced graphics tab QScrollArea* graphics_advanced_scroll_area = new QScrollArea(this); graphics_advanced_scroll_area->setWidgetResizable(true); graphics_advanced_scroll_area->setWidget(graphics_advanced_tab.get()); @@ -104,7 +108,6 @@ tab_group{std::make_shared>()} { ui->tabWidget->addTab(audio_tab.get(), tr("Audio")); ui->tabWidget->addTab(input_tab.get(), tr("Input Profiles")); - // Only show Linux tab on Unix linux_tab->setVisible(false); #ifdef __unix__ linux_tab->setVisible(true); @@ -130,6 +133,11 @@ tab_group{std::make_shared>()} { ConfigurePerGame::~ConfigurePerGame() = default; +void ConfigurePerGame::accept() { + ApplyConfiguration(); + QDialog::accept(); +} + void ConfigurePerGame::ApplyConfiguration() { for (const auto tab : *tab_group) { tab->ApplyConfiguration(); @@ -171,6 +179,83 @@ void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) { LoadConfiguration(); } +void ConfigurePerGame::ApplyStaticTheme() { + QString raw_stylesheet = property("templateStyleSheet").toString(); + QString processed_stylesheet = raw_stylesheet; + + QColor accent_color(Theme::GetAccentColor()); + QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb); + QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb); + + processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color.name(QColor::HexRgb)); + processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover); + processed_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed); + + setStyleSheet(processed_stylesheet); + // Pass the processed stylesheet to the child tabs ONCE + graphics_tab->SetTemplateStyleSheet(processed_stylesheet); + system_tab->SetTemplateStyleSheet(processed_stylesheet); + audio_tab->SetTemplateStyleSheet(processed_stylesheet); + cpu_tab->SetTemplateStyleSheet(processed_stylesheet); + graphics_advanced_tab->SetTemplateStyleSheet(processed_stylesheet); +} + +void ConfigurePerGame::UpdateTheme() { + if (!UISettings::values.enable_rainbow_mode.GetValue()) { + if (rainbow_timer->isActive()) { + rainbow_timer->stop(); + ApplyStaticTheme(); + } + return; + } + + rainbow_hue += 0.01f; + if (rainbow_hue > 1.0f) { + rainbow_hue = 0.0f; + } + + QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8, 1.0); + QColor accent_color_hover = accent_color.lighter(115); + QColor accent_color_pressed = accent_color.darker(120); + + // Efficiently update only the necessary widgets + QString tab_style = QStringLiteral( + "QTabBar::tab:selected { background-color: %1; border-color: %1; }") + .arg(accent_color.name(QColor::HexRgb)); + ui->tabWidget->tabBar()->setStyleSheet(tab_style); + + QString button_style = QStringLiteral( + "QPushButton { background-color: %1; color: #ffffff; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; min-height: 20px; }" + "QPushButton:hover { background-color: %2; }" + "QPushButton:pressed { background-color: %3; }") + .arg(accent_color.name(QColor::HexRgb)) + .arg(accent_color_hover.name(QColor::HexRgb)) + .arg(accent_color_pressed.name(QColor::HexRgb)); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setStyleSheet(button_style); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setStyleSheet(button_style); + if (auto* apply_button = ui->buttonBox->button(QDialogButtonBox::Apply)) { + apply_button->setStyleSheet(button_style); + } + + // Create a temporary full stylesheet for the child tabs to update their internal widgets + QString child_stylesheet = property("templateStyleSheet").toString(); + child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color.name(QColor::HexRgb)); + child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover.name(QColor::HexRgb)); + child_stylesheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed.name(QColor::HexRgb)); + + // Pass the updated stylesheet to the child tabs + graphics_tab->SetTemplateStyleSheet(child_stylesheet); + system_tab->SetTemplateStyleSheet(child_stylesheet); + audio_tab->SetTemplateStyleSheet(child_stylesheet); + cpu_tab->SetTemplateStyleSheet(child_stylesheet); + graphics_advanced_tab->SetTemplateStyleSheet(child_stylesheet); + + if (!rainbow_timer->isActive()) { + rainbow_timer->start(50); // Use a reasonable 50ms interval to prevent lag + } +} + void ConfigurePerGame::LoadConfiguration() { if (file == nullptr) { return; @@ -232,33 +317,27 @@ void ConfigurePerGame::LoadConfiguration() { const auto valueText = ReadableByteSize(file->GetSize()); ui->display_size->setText(valueText); - // Display Build ID(s) if available std::string base_build_id_hex; std::string update_build_id_hex; - // Try to get build ID based on file type const auto file_type = loader->GetFileType(); if (file_type == Loader::FileType::NSO) { - // For NSO files, read the build ID directly from the header if (file->GetSize() >= 0x100) { std::array header_data{}; if (file->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - // Build ID is at offset 0x40 in NSO header std::array build_id{}; std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); base_build_id_hex = Common::HexToString(build_id, false); } } } else if (file_type == Loader::FileType::DeconstructedRomDirectory) { - // For deconstructed ROM directories, read from the main NSO file const auto main_dir = file->GetContainingDirectory(); if (main_dir) { const auto main_nso = main_dir->GetFile("main"); if (main_nso && main_nso->GetSize() >= 0x100) { std::array header_data{}; if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) { - // Build ID is at offset 0x40 in NSO header std::array build_id{}; std::memcpy(build_id.data(), header_data.data() + 0x40, 0x20); base_build_id_hex = Common::HexToString(build_id, false); @@ -266,16 +345,12 @@ void ConfigurePerGame::LoadConfiguration() { } } } else { - // For other file types (XCI, NSP, NCA), try to extract build ID directly try { if (file_type == Loader::FileType::XCI) { - // For XCI files, try to construct with the proper parameters try { - // First try to get the program ID from the XCI to use proper parameters FileSys::XCI xci_temp(file); if (xci_temp.GetStatus() == Loader::ResultStatus::Success) { - // Try to get the program NCA from the secure partition - FileSys::XCI xci(file, title_id, 0); // Use detected title_id + FileSys::XCI xci(file, title_id, 0); if (xci.GetStatus() == Loader::ResultStatus::Success) { auto program_nca = xci.GetNCAByType(FileSys::NCAContentType::Program); if (program_nca && program_nca->GetStatus() == Loader::ResultStatus::Success) { @@ -295,8 +370,6 @@ void ConfigurePerGame::LoadConfiguration() { } } } catch (...) { - // XCI might be encrypted or have other issues - // Fall back to checking if it's installed in the content provider const auto& content_provider = system.GetContentProvider(); auto base_nca = content_provider.GetEntry(title_id, FileSys::ContentRecordType::Program); if (base_nca && base_nca->GetStatus() == Loader::ResultStatus::Success) { @@ -315,7 +388,6 @@ void ConfigurePerGame::LoadConfiguration() { } } } else if (file_type == Loader::FileType::NSP) { - // For NSP files, try to construct and parse directly FileSys::NSP nsp(file); if (nsp.GetStatus() == Loader::ResultStatus::Success) { auto exefs = nsp.GetExeFS(); @@ -332,7 +404,6 @@ void ConfigurePerGame::LoadConfiguration() { } } } else if (file_type == Loader::FileType::NCA) { - // For NCA files, try to construct and parse directly FileSys::NCA nca(file); if (nca.GetStatus() == Loader::ResultStatus::Success) { auto exefs = nca.GetExeFS(); @@ -350,23 +421,15 @@ void ConfigurePerGame::LoadConfiguration() { } } } catch (...) { - // If anything fails, continue without the build ID } } - // Try to get update build ID from patch manager and content provider try { - // Method 1: Try through patch manager (more reliable for updates) const FileSys::PatchManager pm_update{title_id, system.GetFileSystemController(), system.GetContentProvider()}; - // Check if patch manager has update information const auto update_version = pm_update.GetGameVersion(); if (update_version.has_value() && update_version.value() > 0) { - // There's an update, try to get its build ID through the patch manager - // The patch manager should have access to the update NCA - - // Try to get the update NCA through the patch manager's content provider const auto& content_provider = system.GetContentProvider(); const auto update_title_id = FileSys::GetUpdateTitleID(title_id); auto update_nca = content_provider.GetEntry(update_title_id, FileSys::ContentRecordType::Program); @@ -387,7 +450,6 @@ void ConfigurePerGame::LoadConfiguration() { } } - // Method 2: If patch manager approach didn't work, try direct content provider access if (update_build_id_hex.empty()) { const auto& content_provider = system.GetContentProvider(); const auto update_title_id = FileSys::GetUpdateTitleID(title_id); @@ -409,47 +471,35 @@ void ConfigurePerGame::LoadConfiguration() { } } - // Method 3: Try to use the patch manager's GetPatches to detect updates if (update_build_id_hex.empty()) { const auto patches = pm_update.GetPatches(); for (const auto& patch : patches) { if (patch.type == FileSys::PatchType::Update && patch.enabled) { - // There's an enabled update patch, but we couldn't get its build ID - // This indicates an update is available but not currently loaded break; } } } } catch (...) { - // If update build ID extraction fails, continue with just base } - // Try to get the actual running build ID from system (this will be the update if one is applied) const auto& system_build_id = system.GetApplicationProcessBuildID(); const auto system_build_id_hex = Common::HexToString(system_build_id, false); - // If we have a system build ID and it's different from the base, it's likely the update if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) { if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) { - // System build ID is different from base, so it's the update update_build_id_hex = system_build_id_hex; } else if (base_build_id_hex.empty()) { - // No base build ID found, use system as base base_build_id_hex = system_build_id_hex; } } - // Additional check: if we still don't have an update build ID but we know there's an update - // (from the Add-Ons tab showing v1.0.1 etc), try alternative detection methods bool update_detected = false; if (update_build_id_hex.empty() && !base_build_id_hex.empty()) { - // Check if the patch manager indicates an update is available const auto update_version = pm.GetGameVersion(); if (update_version.has_value() && update_version.value() > 0) { update_detected = true; } - // Also check patches const auto patches = pm.GetPatches(); for (const auto& patch : patches) { if (patch.type == FileSys::PatchType::Update && patch.enabled) { @@ -459,18 +509,15 @@ void ConfigurePerGame::LoadConfiguration() { } } - // Display the Build IDs in separate fields bool has_base = !base_build_id_hex.empty() && base_build_id_hex != std::string(64, '0'); bool has_update = !update_build_id_hex.empty() && update_build_id_hex != std::string(64, '0'); - // Set Base Build ID if (has_base) { ui->display_build_id->setText(QString::fromStdString(base_build_id_hex)); } else { ui->display_build_id->setText(tr("Not Available")); } - // Set Update Build ID if (has_update) { ui->display_update_build_id->setText(QString::fromStdString(update_build_id_hex)); } else if (update_detected) { diff --git a/src/citron/configuration/configure_per_game.h b/src/citron/configuration/configure_per_game.h index 5e726d401..3591347fb 100644 --- a/src/citron/configuration/configure_per_game.h +++ b/src/citron/configuration/configure_per_game.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -19,11 +20,11 @@ #include "citron/configuration/shared_translation.h" namespace Core { -class System; + class System; } namespace InputCommon { -class InputSubsystem; + class InputSubsystem; } class ConfigurePerGameAddons; @@ -40,9 +41,10 @@ class QStandardItem; class QStandardItemModel; class QTreeView; class QVBoxLayout; +class QTimer; // Forward declaration for the timer namespace Ui { -class ConfigurePerGame; + class ConfigurePerGame; } class ConfigurePerGame : public QDialog { @@ -55,25 +57,27 @@ public: Core::System& system_); ~ConfigurePerGame() override; - /// Save all button configurations to settings file void ApplyConfiguration(); - void LoadFromFile(FileSys::VirtualFile file_); +public slots: + void accept() override; + private: void changeEvent(QEvent* event) override; void RetranslateUI(); - void HandleApplyButtonClicked(); - void LoadConfiguration(); + // New, efficient theme update functions + void ApplyStaticTheme(); + void UpdateTheme(); + std::unique_ptr ui; FileSys::VirtualFile file; u64 title_id; QGraphicsScene* scene; - std::unique_ptr game_config; Core::System& system; @@ -88,4 +92,7 @@ private: std::unique_ptr input_tab; std::unique_ptr linux_tab; std::unique_ptr system_tab; + + QTimer* rainbow_timer; + float rainbow_hue = 0.0f; }; diff --git a/src/citron/configuration/configure_per_game.ui b/src/citron/configuration/configure_per_game.ui index 1325527b9..c269478b5 100644 --- a/src/citron/configuration/configure_per_game.ui +++ b/src/citron/configuration/configure_per_game.ui @@ -19,6 +19,417 @@ Dialog + + QDialog { + background-color: #2b2b2b; + color: #ffffff; + } + + QWidget { + background-color: #2b2b2b; + color: #ffffff; + } + + QStackedWidget { + background-color: #2b2b2b; + border: 1px solid #3d3d3d; + border-radius: 8px; + margin: 0px; + padding: 0px; + } + + QScrollArea { + background-color: #2b2b2b; + border: none; + border-radius: 8px; + } + + QScrollArea > QWidget > QWidget { + background-color: #2b2b2b; + } + + QScrollBar:vertical { + background-color: #3d3d3d; + width: 14px; + border-radius: 7px; + margin: 2px; + } + + QScrollBar::handle:vertical { + background-color: #5d5d5d; + border-radius: 6px; + min-height: 30px; + margin: 1px; + } + + QScrollBar::handle:vertical:hover { + background-color: %%ACCENT_COLOR%%; + } + + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + border: none; + background: none; + height: 0px; + } + + QScrollBar:horizontal { + background-color: #3d3d3d; + height: 14px; + border-radius: 7px; + margin: 2px; + } + + QScrollBar::handle:horizontal { + background-color: #5d5d5d; + border-radius: 6px; + min-width: 30px; + margin: 1px; + } + + QScrollBar::handle:horizontal:hover { + background-color: %%ACCENT_COLOR%%; + } + + QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { + border: none; + background: none; + width: 0px; + } + + QPushButton.tabButton { + background-color: #383838; + color: #ffffff; + padding: 10px 14px; + margin: 2px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + min-width: 85px; + max-width: 160px; + font-weight: 500; + border: 1px solid #3d3d3d; + text-align: center; + } + + QPushButton.tabButton:checked { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + font-weight: bold; + border-color: %%ACCENT_COLOR%%; + } + + QPushButton.tabButton:hover:!checked { + background-color: #4d4d4d; + border-color: #5d5d5d; + } + + QPushButton.tabButton:pressed { + background-color: %%ACCENT_COLOR_PRESSED%%; + } + + QTabWidget { + background-color: #2b2b2b; + border: none; + } + + QTabWidget::pane { + border: 1px solid #3d3d3d; + background-color: #2b2b2b; + border-radius: 8px; + margin: 0px; + padding: 0px; + } + + QTabWidget::tab-bar { + alignment: left; + } + + QTabBar { + background-color: #2b2b2b; + border: none; + } + + QTabBar::tab { + background-color: #383838; + color: #ffffff; + padding: 8px 14px; + margin-right: 2px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + min-width: 85px; + font-weight: 500; + border: 1px solid #3d3d3d; + border-bottom: none; + } + + QTabBar::tab:selected { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + font-weight: bold; + border-color: %%ACCENT_COLOR%%; + } + + QTabBar::tab:hover:!selected { + background-color: #4d4d4d; + border-color: #5d5d5d; + } + + QTabBar QToolButton { + background-color: #383838; + border: 1px solid #3d3d3d; + border-radius: 4px; + padding: 4px; + margin: 2px; + } + + QTabBar QToolButton:hover { + background-color: #4d4d4d; + border-color: %%ACCENT_COLOR%%; + } + + QTabBar::scroller { + width: 30px; + } + + QGroupBox { + font-weight: bold; + border: 1px solid #3d3d3d; + border-radius: 8px; + margin-top: 12px; + padding-top: 12px; + background-color: #2b2b2b; + color: #ffffff; + } + + QGroupBox::title { + subcontrol-origin: margin; + left: 12px; + padding: 0 8px 0 8px; + color: #ffffff; + font-weight: bold; + } + + QCheckBox { + color: #ffffff; + spacing: 10px; + padding: 4px; + background-color: transparent; + } + + QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid #5d5d5d; + border-radius: 4px; + background-color: #3d3d3d; + } + + QCheckBox::indicator:checked { + background-color: %%ACCENT_COLOR%%; + border-color: %%ACCENT_COLOR%%; + } + + QCheckBox::indicator:hover { + border-color: %%ACCENT_COLOR%%; + } + + QComboBox { + background-color: #3d3d3d; + border: 1px solid #5d5d5d; + border-radius: 6px; + padding: 8px 12px; + color: #ffffff; + min-width: 120px; + min-height: 28px; + selection-background-color: %%ACCENT_COLOR%%; + } + + QComboBox:hover { + border-color: %%ACCENT_COLOR%%; + background-color: #404040; + } + + QComboBox:focus { + border-color: %%ACCENT_COLOR%%; + background-color: #404040; + } + + QComboBox::drop-down { + border: none; + width: 25px; + subcontrol-origin: padding; + subcontrol-position: top right; + background-color: transparent; + } + + QComboBox QAbstractItemView { + background-color: #3d3d3d; + border: 1px solid %%ACCENT_COLOR%%; + selection-background-color: %%ACCENT_COLOR%%; + color: #ffffff; + outline: none; + } + + QComboBox QAbstractItemView::item { + padding: 8px; + border: none; + background-color: transparent; + } + + QComboBox QAbstractItemView::item:selected { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + } + + QComboBox QAbstractItemView::item:hover { + background-color: %%ACCENT_COLOR_HOVER%%; + color: #ffffff; + } + + QLineEdit { + background-color: #3d3d3d; + border: 1px solid #5d5d5d; + border-radius: 6px; + padding: 8px 12px; + color: #ffffff; + min-height: 20px; + selection-background-color: %%ACCENT_COLOR%%; + } + + QLineEdit:focus { + border-color: %%ACCENT_COLOR%%; + background-color: #404040; + } + + QPushButton { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + border: none; + padding: 10px 20px; + border-radius: 6px; + font-weight: bold; + min-height: 20px; + } + + QPushButton:hover { + background-color: %%ACCENT_COLOR_HOVER%%; + } + + QPushButton:pressed { + background-color: %%ACCENT_COLOR_PRESSED%%; + } + + QPushButton:disabled { + background-color: #5d5d5d; + color: #8d8d8d; + } + + QToolButton { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + border: none; + padding: 8px 12px; + border-radius: 6px; + font-weight: bold; + min-width: 32px; + min-height: 24px; + } + + QToolButton:hover { + background-color: %%ACCENT_COLOR_HOVER%%; + } + + QToolButton:pressed { + background-color: %%ACCENT_COLOR_PRESSED%%; + } + + QLabel { + color: #ffffff; + background-color: transparent; + padding: 2px; + } + + QListWidget { + background-color: #3d3d3d; + border: 1px solid #5d5d5d; + border-radius: 6px; + color: #ffffff; + padding: 4px; + } + + QListWidget::item { + padding: 8px; + border-radius: 4px; + margin: 1px; + } + + QListWidget::item:selected { + background-color: %%ACCENT_COLOR%%; + color: #ffffff; + } + + QListWidget::item:hover:!selected { + background-color: #4d4d4d; + } + + QSlider::groove:horizontal { + border: 1px solid #5d5d5d; + height: 8px; + background-color: #3d3d3d; + border-radius: 4px; + } + + QSlider::handle:horizontal { + background-color: %%ACCENT_COLOR%%; + border: 1px solid %%ACCENT_COLOR%%; + width: 18px; + margin: -5px 0; + border-radius: 9px; + } + + QSlider::handle:horizontal:hover { + background-color: %%ACCENT_COLOR_HOVER%%; + } + + QSpinBox, QDoubleSpinBox { + background-color: #3d3d3d; + border: 1px solid #5d5d5d; + border-radius: 6px; + padding: 6px; + color: #ffffff; + min-height: 20px; + } + + QSpinBox:focus, QDoubleSpinBox:focus { + border-color: %%ACCENT_COLOR%%; + background-color: #404040; + } + + QRadioButton { + color: #ffffff; + spacing: 8px; + padding: 4px; + } + + QRadioButton::indicator { + width: 16px; + height: 16px; + border: 2px solid #5d5d5d; + border-radius: 8px; + background-color: #3d3d3d; + } + + QRadioButton::indicator:checked { + background-color: %%ACCENT_COLOR%%; + border-color: %%ACCENT_COLOR%%; + } + + QRadioButton::indicator:hover { + border-color: %%ACCENT_COLOR%%; + } + + @@ -65,7 +476,6 @@ 0 - Info @@ -278,7 +688,6 @@ - diff --git a/src/citron/configuration/configure_system.cpp b/src/citron/configuration/configure_system.cpp index 685772858..d50907fab 100644 --- a/src/citron/configuration/configure_system.cpp +++ b/src/citron/configuration/configure_system.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -20,6 +21,7 @@ #include "citron/configuration/configuration_shared.h" #include "citron/configuration/configure_system.h" #include "citron/configuration/shared_widget.h" +#include "citron/theme.h" // Added for Theme::GetAccentColor constexpr std::array LOCALE_BLOCKLIST{ // pzzefezrpnkzeidfej @@ -47,12 +49,12 @@ static bool IsValidLocale(u32 region_index, u32 language_index) { ConfigureSystem::ConfigureSystem(Core::System& system_, std::shared_ptr> group_, const ConfigurationShared::Builder& builder, QWidget* parent) - : Tab(group_, parent), ui{std::make_unique()}, system{system_} { +: Tab(group_, parent), ui{std::make_unique()}, system{system_} { ui->setupUi(this); const auto posix_time = std::chrono::system_clock::now().time_since_epoch(); const auto current_time_s = - std::chrono::duration_cast(posix_time).count(); + std::chrono::duration_cast(posix_time).count(); previous_time = current_time_s + Settings::values.custom_rtc_offset.GetValue(); Setup(builder); @@ -65,8 +67,8 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, if (!valid_locale) { ui->label_warn_invalid_locale->setText( tr("Warning: \"%1\" is not a valid language for region \"%2\"") - .arg(combo_language->currentText()) - .arg(combo_region->currentText())); + .arg(combo_language->currentText()) + .arg(combo_region->currentText())); } }; @@ -129,9 +131,9 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) { if (setting->Id() == Settings::values.use_docked_mode.Id() && Settings::IsConfiguringGlobal()) { continue; - } + } - ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs); + ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs); if (widget == nullptr) { continue; @@ -164,14 +166,14 @@ void ConfigureSystem::Setup(const ConfigurationShared::Builder& builder) { } switch (setting->GetCategory()) { - case Settings::Category::Core: - core_hold.emplace(setting->Id(), widget); - break; - case Settings::Category::System: - system_hold.emplace(setting->Id(), widget); - break; - default: - widget->deleteLater(); + case Settings::Category::Core: + core_hold.emplace(setting->Id(), widget); + break; + case Settings::Category::System: + system_hold.emplace(setting->Id(), widget); + break; + default: + widget->deleteLater(); } } for (const auto& [label, widget] : core_hold) { @@ -204,3 +206,32 @@ void ConfigureSystem::ApplyConfiguration() { } UpdateRtcTime(); } + +QString ConfigureSystem::GetTemplateStyleSheet() const { + return m_template_style_sheet; +} + +void ConfigureSystem::SetTemplateStyleSheet(const QString& sheet) { + if (m_template_style_sheet == sheet) { + return; + } + + m_template_style_sheet = sheet; + + // Get the current accent color from the theme manager + const QColor accent_color(Theme::GetAccentColor()); + const QColor accent_color_hover = accent_color.lighter(115); + const QColor accent_color_pressed = accent_color.darker(115); + + QString final_style = m_template_style_sheet; + + // Replace all placeholders with the actual color values + final_style.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color.name(QColor::HexRgb)); + final_style.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover.name(QColor::HexRgb)); + final_style.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed.name(QColor::HexRgb)); + + // Apply the finished stylesheet to THIS widget and all its children + this->setStyleSheet(final_style); + + emit TemplateStyleSheetChanged(); +} diff --git a/src/citron/configuration/configure_system.h b/src/citron/configuration/configure_system.h index a1a3a799d..e795a9d1b 100644 --- a/src/citron/configuration/configure_system.h +++ b/src/citron/configuration/configure_system.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -7,6 +8,7 @@ #include #include +#include // Added for stylesheet property #include #include "citron/configuration/configuration_shared.h" @@ -15,20 +17,23 @@ class QLineEdit; class QComboBox; class QDateTimeEdit; namespace Core { -class System; + class System; } namespace Ui { -class ConfigureSystem; + class ConfigureSystem; } namespace ConfigurationShared { -class Builder; + class Builder; } class ConfigureSystem : public ConfigurationShared::Tab { Q_OBJECT + // This property allows the main UI file to pass its stylesheet to this widget + Q_PROPERTY(QString templateStyleSheet READ GetTemplateStyleSheet WRITE SetTemplateStyleSheet NOTIFY TemplateStyleSheetChanged) + public: explicit ConfigureSystem(Core::System& system_, std::shared_ptr> group, @@ -39,6 +44,13 @@ public: void ApplyConfiguration() override; void SetConfiguration() override; + // These functions get and set the stylesheet property + QString GetTemplateStyleSheet() const; + void SetTemplateStyleSheet(const QString& sheet); + +signals: + void TemplateStyleSheetChanged(); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); @@ -60,4 +72,7 @@ private: QDateTimeEdit* date_rtc; QSpinBox* date_rtc_offset; u64 previous_time; + + // This variable will hold the raw stylesheet string + QString m_template_style_sheet; };