mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-21 19:43:34 +00:00
Merge branch 'fix_and_feat_accent_colors' into 'main'
fix(ui): Correct accent color theming for Graphics and Properties See merge request citron/emulator!82
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
</string>
|
||||
</property>
|
||||
</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>8</number>
|
||||
|
||||
@@ -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 <map>
|
||||
@@ -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<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
const ConfigurationShared::Builder& builder, QWidget* parent)
|
||||
: Tab(group_, parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
|
||||
: Tab(group_, parent), ui(std::make_unique<Ui::ConfigureAudio>()), 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<Settings::AudioEngine>(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<Settings::AudioEngine>(sink_combo_box->itemText(sink_index).toStdString());
|
||||
Settings::ToEnum<Settings::AudioEngine>(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();
|
||||
}
|
||||
|
||||
@@ -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 <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QString> // Added for stylesheet property
|
||||
#include <QWidget>
|
||||
#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<std::vector<ConfigurationShared::Tab*>> 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;
|
||||
};
|
||||
|
||||
@@ -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 <memory>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
@@ -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<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
const ConfigurationShared::Builder& builder, QWidget* parent)
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureCpu>()}, system{system_},
|
||||
combobox_translations(builder.ComboboxTranslations()) {
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureCpu>()}, 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<int>(&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<Settings::CpuAccuracy>(
|
||||
combobox_translations.at(Settings::EnumMetadata<Settings::CpuAccuracy>::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();
|
||||
}
|
||||
|
||||
@@ -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 <memory>
|
||||
#include <vector>
|
||||
#include <QString> // Added for stylesheet property
|
||||
#include <QWidget>
|
||||
#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<std::vector<ConfigurationShared::Tab*>> 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;
|
||||
};
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<VkDeviceInfo::Record>& 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;
|
||||
};
|
||||
|
||||
@@ -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 <vector>
|
||||
#include <QLabel>
|
||||
#include <qnamespace.h>
|
||||
@@ -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<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
const ConfigurationShared::Builder& builder, QWidget* parent)
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphicsAdvanced>()}, system{system_} {
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphicsAdvanced>()}, system{system_} {
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
@@ -35,27 +36,27 @@ void ConfigureGraphicsAdvanced::Setup(const ConfigurationShared::Builder& builde
|
||||
std::map<u32, QWidget*> 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();
|
||||
}
|
||||
|
||||
@@ -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 <memory>
|
||||
#include <vector>
|
||||
#include <QString> // Added for stylesheet property
|
||||
#include <QWidget>
|
||||
#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<std::vector<ConfigurationShared::Tab*>> 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<std::function<void(bool)>> apply_funcs;
|
||||
|
||||
QWidget* checkbox_enable_compute_pipelines{};
|
||||
|
||||
// This variable will hold the raw stylesheet string
|
||||
QString m_template_style_sheet;
|
||||
};
|
||||
|
||||
@@ -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 <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
@@ -14,9 +15,11 @@
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QString>
|
||||
#include <QTabBar>
|
||||
#include <QTimer>
|
||||
|
||||
#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<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
|
||||
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
|
||||
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} ,
|
||||
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<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
|
||||
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<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
|
||||
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<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
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<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
|
||||
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<u8, 0x100> header_data{};
|
||||
if (file->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
|
||||
// Build ID is at offset 0x40 in NSO header
|
||||
std::array<u8, 0x20> 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<u8, 0x100> header_data{};
|
||||
if (main_nso->ReadBytes(header_data.data(), 0x100, 0) == 0x100) {
|
||||
// Build ID is at offset 0x40 in NSO header
|
||||
std::array<u8, 0x20> 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) {
|
||||
|
||||
@@ -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::ConfigurePerGame> ui;
|
||||
FileSys::VirtualFile file;
|
||||
u64 title_id;
|
||||
|
||||
QGraphicsScene* scene;
|
||||
|
||||
std::unique_ptr<QtConfig> game_config;
|
||||
|
||||
Core::System& system;
|
||||
@@ -88,4 +92,7 @@ private:
|
||||
std::unique_ptr<ConfigureInputPerGame> input_tab;
|
||||
std::unique_ptr<ConfigureLinuxTab> linux_tab;
|
||||
std::unique_ptr<ConfigureSystem> system_tab;
|
||||
|
||||
QTimer* rainbow_timer;
|
||||
float rainbow_hue = 0.0f;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,417 @@
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="templateStyleSheet" stdset="0">
|
||||
<string>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%%;
|
||||
}
|
||||
</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
@@ -65,7 +476,6 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<!-- The original GroupBox is now INSIDE the scroll area -->
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Info</string>
|
||||
@@ -278,7 +688,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<!-- END OF NEW SCROLL AREA -->
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="VerticalLayout">
|
||||
|
||||
@@ -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 <chrono>
|
||||
@@ -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<u32, 7> LOCALE_BLOCKLIST{
|
||||
// pzzefezrpnkzeidfej
|
||||
@@ -47,12 +49,12 @@ static bool IsValidLocale(u32 region_index, u32 language_index) {
|
||||
ConfigureSystem::ConfigureSystem(Core::System& system_,
|
||||
std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
const ConfigurationShared::Builder& builder, QWidget* parent)
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureSystem>()}, system{system_} {
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureSystem>()}, 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<std::chrono::seconds>(posix_time).count();
|
||||
std::chrono::duration_cast<std::chrono::seconds>(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();
|
||||
}
|
||||
|
||||
@@ -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 <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QString> // Added for stylesheet property
|
||||
#include <QWidget>
|
||||
#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<std::vector<ConfigurationShared::Tab*>> 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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user