diff --git a/src/citron/CMakeLists.txt b/src/citron/CMakeLists.txt index 2feb192e2..d2ae1286b 100644 --- a/src/citron/CMakeLists.txt +++ b/src/citron/CMakeLists.txt @@ -233,6 +233,8 @@ add_executable(citron util/performance_overlay.h util/multiplayer_room_overlay.cpp util/multiplayer_room_overlay.h + util/rainbow_style.cpp + util/rainbow_style.h util/vram_overlay.cpp util/vram_overlay.h util/sequence_dialog/sequence_dialog.cpp @@ -455,7 +457,7 @@ endif() target_link_libraries(citron PRIVATE Vulkan::Headers) if (NOT WIN32) - target_include_directories(citron PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + target_link_libraries(citron PRIVATE Qt6::GuiPrivate) endif() if (UNIX AND NOT APPLE) target_link_libraries(citron PRIVATE Qt6::DBus) diff --git a/src/citron/configuration/configure_dialog.cpp b/src/citron/configuration/configure_dialog.cpp index ff656fc72..6cff3c14e 100644 --- a/src/citron/configuration/configure_dialog.cpp +++ b/src/citron/configuration/configure_dialog.cpp @@ -44,6 +44,7 @@ #include "citron/configuration/configure_ui.h" #include "citron/configuration/configure_web.h" #include "citron/configuration/style_animation_event_filter.h" +#include "citron/util/rainbow_style.h" #include "citron/game_list.h" #include "citron/hotkeys.h" #include "citron/main.h" @@ -99,7 +100,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, network_tab{std::make_unique(system_, this)}, profile_tab{std::make_unique(system_, this)}, system_tab{std::make_unique(system_, nullptr, *builder, this)}, - web_tab{std::make_unique(this)}, rainbow_timer{new QTimer(this)} { + web_tab{std::make_unique(this)} { if (auto* main_window = qobject_cast(parent)) { connect(filesystem_tab.get(), &ConfigureFilesystem::RequestGameListRefresh, @@ -169,7 +170,6 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, connect(tab_button_group.get(), qOverload(&QButtonGroup::idClicked), this, &ConfigureDialog::AnimateTabSwitch); connect(ui_tab.get(), &ConfigureUi::themeChanged, this, &ConfigureDialog::UpdateTheme); connect(ui_tab.get(), &ConfigureUi::UIPositioningChanged, this, &ConfigureDialog::SetUIPositioning); - connect(rainbow_timer, &QTimer::timeout, this, &ConfigureDialog::UpdateTheme); web_tab->SetWebServiceConfigEnabled(enable_web_config); hotkeys_tab->Populate(registry); input_tab->Initialize(input_subsystem); @@ -192,51 +192,70 @@ ConfigureDialog::~ConfigureDialog() { } void ConfigureDialog::UpdateTheme() { - QString accent_color_str; - if (UISettings::values.enable_rainbow_mode.GetValue()) { - rainbow_hue += 0.003f; - if (rainbow_hue > 1.0f) rainbow_hue = 0.0f; - QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8f, 1.0f); - accent_color_str = accent_color.name(QColor::HexRgb); - if (!rainbow_timer->isActive()) rainbow_timer->start(150); - } else { - if (rainbow_timer->isActive()) rainbow_timer->stop(); - accent_color_str = Theme::GetAccentColor(); - } - QColor accent_color(accent_color_str); - const QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb); - const QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb); + const bool is_rainbow = UISettings::values.enable_rainbow_mode.GetValue(); + const QString accent = Theme::GetAccentColor(); const bool is_dark = IsDarkMode(); - const QString bg_color = is_dark ? QStringLiteral("#2b2b2b") : QStringLiteral("#ffffff"); - const QString text_color = is_dark ? QStringLiteral("#ffffff") : QStringLiteral("#000000"); - const QString secondary_bg_color = is_dark ? QStringLiteral("#3d3d3d") : QStringLiteral("#f0f0f0"); - const QString tertiary_bg_color = is_dark ? QStringLiteral("#5d5d5d") : QStringLiteral("#d3d3d3"); - const QString button_bg_color = is_dark ? QStringLiteral("#383838") : QStringLiteral("#e1e1e1"); - const QString hover_bg_color = is_dark ? QStringLiteral("#4d4d4d") : QStringLiteral("#e8f0fe"); - const QString focus_bg_color = is_dark ? QStringLiteral("#404040") : QStringLiteral("#e8f0fe"); - const QString disabled_text_color = is_dark ? QStringLiteral("#8d8d8d") : QStringLiteral("#a0a0a0"); - static QString cached_template_style_sheet; - if (cached_template_style_sheet.isEmpty()) { - cached_template_style_sheet = property("templateStyleSheet").toString(); - } - QString style_sheet = cached_template_style_sheet; - style_sheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color_str); - style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover); - style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed); - style_sheet.replace(QStringLiteral("%%BACKGROUND_COLOR%%"), bg_color); - style_sheet.replace(QStringLiteral("%%TEXT_COLOR%%"), text_color); - style_sheet.replace(QStringLiteral("%%SECONDARY_BG_COLOR%%"), secondary_bg_color); - style_sheet.replace(QStringLiteral("%%TERTIARY_BG_COLOR%%"), tertiary_bg_color); - style_sheet.replace(QStringLiteral("%%BUTTON_BG_COLOR%%"), button_bg_color); - style_sheet.replace(QStringLiteral("%%HOVER_BG_COLOR%%"), hover_bg_color); - style_sheet.replace(QStringLiteral("%%FOCUS_BG_COLOR%%"), focus_bg_color); - style_sheet.replace(QStringLiteral("%%DISABLED_TEXT_COLOR%%"), disabled_text_color); + + const QString bg = is_dark ? QStringLiteral("#2b2b2b") : QStringLiteral("#ffffff"); + const QString txt = is_dark ? QStringLiteral("#ffffff") : QStringLiteral("#000000"); + const QString sec = is_dark ? QStringLiteral("#3d3d3d") : QStringLiteral("#f0f0f0"); + const QString ter = is_dark ? QStringLiteral("#5d5d5d") : QStringLiteral("#d3d3d3"); + const QString b_bg = is_dark ? QStringLiteral("#383838") : QStringLiteral("#e1e1e1"); + const QString h_bg = is_dark ? QStringLiteral("#4d4d4d") : QStringLiteral("#e8f0fe"); + const QString f_bg = is_dark ? QStringLiteral("#404040") : QStringLiteral("#e8f0fe"); + const QString d_txt = is_dark ? QStringLiteral("#8d8d8d") : QStringLiteral("#a0a0a0"); + + static QString cached_template; + if (cached_template.isEmpty()) cached_template = property("templateStyleSheet").toString(); + QString style_sheet = cached_template; + + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), Theme::GetAccentColorHover()); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), Theme::GetAccentColorPressed()); + style_sheet.replace(QStringLiteral("%%BACKGROUND_COLOR%%"), bg); + style_sheet.replace(QStringLiteral("%%TEXT_COLOR%%"), txt); + style_sheet.replace(QStringLiteral("%%SECONDARY_BG_COLOR%%"), sec); + style_sheet.replace(QStringLiteral("%%TERTIARY_BG_COLOR%%"), ter); + style_sheet.replace(QStringLiteral("%%BUTTON_BG_COLOR%%"), b_bg); + style_sheet.replace(QStringLiteral("%%HOVER_BG_COLOR%%"), h_bg); + style_sheet.replace(QStringLiteral("%%FOCUS_BG_COLOR%%"), f_bg); + style_sheet.replace(QStringLiteral("%%DISABLED_TEXT_COLOR%%"), d_txt); + + style_sheet += QStringLiteral( + "QSlider::handle:horizontal { background-color: %1; }" + "QCheckBox::indicator:checked { background-color: %1; border-color: %1; }" + ).arg(accent); + setStyleSheet(style_sheet); + 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); + + if (is_rainbow) { + if (!rainbow_timer) { + rainbow_timer = new QTimer(this); + connect(rainbow_timer, &QTimer::timeout, this, [this] { + QString hue_hex = RainbowStyle::GetCurrentHighlightColor().name(); + QString sidebar_css = QStringLiteral( + "QPushButton.tabButton { border: 2px solid transparent; }" + "QPushButton.tabButton:checked { color: %1; border: 2px solid %1; }" + "QPushButton.tabButton:hover { border: 2px solid %1; }" + "QPushButton.tabButton:pressed { background-color: %1; color: #ffffff; }" + ).arg(hue_hex); + + if (ui->topButtonWidget) ui->topButtonWidget->setStyleSheet(sidebar_css); + if (ui->horizontalNavWidget) ui->horizontalNavWidget->setStyleSheet(sidebar_css); + }); + } + rainbow_timer->start(33); + } else if (rainbow_timer) { + rainbow_timer->stop(); + if (ui->topButtonWidget) ui->topButtonWidget->setStyleSheet({}); + if (ui->horizontalNavWidget) ui->horizontalNavWidget->setStyleSheet({}); + } } void ConfigureDialog::SetUIPositioning(const QString& positioning) { diff --git a/src/citron/configuration/configure_dialog.h b/src/citron/configuration/configure_dialog.h index 89f0b8bbb..a5cde5c00 100644 --- a/src/citron/configuration/configure_dialog.h +++ b/src/citron/configuration/configure_dialog.h @@ -91,6 +91,5 @@ private: std::unique_ptr web_tab; std::unique_ptr tab_button_group; std::vector tab_buttons; - QTimer* rainbow_timer; - float rainbow_hue = 0.0f; + QTimer* rainbow_timer{nullptr}; }; diff --git a/src/citron/configuration/configure_per_game.cpp b/src/citron/configuration/configure_per_game.cpp index 9eb2324ad..2f0ad8d6d 100644 --- a/src/citron/configuration/configure_per_game.cpp +++ b/src/citron/configuration/configure_per_game.cpp @@ -63,6 +63,7 @@ #include "citron/configuration/configure_per_game_addons.h" #include "citron/configuration/configure_per_game_cheats.h" #include "citron/configuration/configure_system.h" +#include "citron/util/rainbow_style.h" #include "citron/theme.h" #include "citron/uisettings.h" #include "citron/util/util.h" @@ -100,8 +101,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st : QDialog(parent), ui(std::make_unique()), title_id{title_id_}, file_name{file_name_}, system{system_}, builder{std::make_unique(this, !system_.IsPoweredOn())}, - tab_group{std::make_shared>()}, - rainbow_timer{new QTimer(this)} { + tab_group{std::make_shared>() } { ui->setupUi(this); @@ -131,7 +131,6 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st } UpdateTheme(); - connect(rainbow_timer, &QTimer::timeout, this, &ConfigurePerGame::UpdateTheme); auto* animation_filter = new StyleAnimationEventFilter(this); @@ -256,68 +255,69 @@ void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) { } void ConfigurePerGame::UpdateTheme() { - QString accent_color_str; - if (UISettings::values.enable_rainbow_mode.GetValue()) { - rainbow_hue += 0.003f; - if (rainbow_hue > 1.0f) { - rainbow_hue = 0.0f; - } - QColor accent_color = QColor::fromHsvF(rainbow_hue, 0.8f, 1.0f); - accent_color_str = accent_color.name(QColor::HexRgb); - if (!rainbow_timer->isActive()) { - rainbow_timer->start(150); - } - } else { - if (rainbow_timer->isActive()) { - rainbow_timer->stop(); - } - accent_color_str = Theme::GetAccentColor(); - } - - QColor accent_color(accent_color_str); - const QString accent_color_hover = accent_color.lighter(115).name(QColor::HexRgb); - const QString accent_color_pressed = accent_color.darker(120).name(QColor::HexRgb); - + const bool is_rainbow = UISettings::values.enable_rainbow_mode.GetValue(); + const QString accent = Theme::GetAccentColor(); const bool is_dark = IsDarkMode(); - const QString bg_color = is_dark ? QStringLiteral("#2b2b2b") : QStringLiteral("#ffffff"); - const QString text_color = is_dark ? QStringLiteral("#ffffff") : QStringLiteral("#000000"); - const QString secondary_bg_color = is_dark ? QStringLiteral("#3d3d3d") : QStringLiteral("#f0f0f0"); - const QString tertiary_bg_color = is_dark ? QStringLiteral("#5d5d5d") : QStringLiteral("#d3d3d3"); - const QString button_bg_color = is_dark ? QStringLiteral("#383838") : QStringLiteral("#e1e1e1"); - const QString hover_bg_color = is_dark ? QStringLiteral("#4d4d4d") : QStringLiteral("#e8f0fe"); - const QString focus_bg_color = is_dark ? QStringLiteral("#404040") : QStringLiteral("#e8f0fe"); - const QString disabled_text_color = is_dark ? QStringLiteral("#8d8d8d") : QStringLiteral("#a0a0a0"); - static QString cached_template_style_sheet; - if (cached_template_style_sheet.isEmpty()) { - cached_template_style_sheet = property("templateStyleSheet").toString(); - } + const QString bg = is_dark ? QStringLiteral("#2b2b2b") : QStringLiteral("#ffffff"); + const QString txt = is_dark ? QStringLiteral("#ffffff") : QStringLiteral("#000000"); + const QString sec = is_dark ? QStringLiteral("#3d3d3d") : QStringLiteral("#f0f0f0"); + const QString ter = is_dark ? QStringLiteral("#5d5d5d") : QStringLiteral("#d3d3d3"); + const QString b_bg = is_dark ? QStringLiteral("#383838") : QStringLiteral("#e1e1e1"); + const QString h_bg = is_dark ? QStringLiteral("#4d4d4d") : QStringLiteral("#e8f0fe"); + const QString f_bg = is_dark ? QStringLiteral("#404040") : QStringLiteral("#e8f0fe"); + const QString d_txt = is_dark ? QStringLiteral("#8d8d8d") : QStringLiteral("#a0a0a0"); - QString style_sheet = cached_template_style_sheet; + static QString cached_template; + if (cached_template.isEmpty()) cached_template = property("templateStyleSheet").toString(); + QString style_sheet = cached_template; - // Replace accent colors (existing logic) - style_sheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent_color_str); - style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), accent_color_hover); - style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), accent_color_pressed); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR%%"), accent); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_HOVER%%"), Theme::GetAccentColorHover()); + style_sheet.replace(QStringLiteral("%%ACCENT_COLOR_PRESSED%%"), Theme::GetAccentColorPressed()); + style_sheet.replace(QStringLiteral("%%BACKGROUND_COLOR%%"), bg); + style_sheet.replace(QStringLiteral("%%TEXT_COLOR%%"), txt); + style_sheet.replace(QStringLiteral("%%SECONDARY_BG_COLOR%%"), sec); + style_sheet.replace(QStringLiteral("%%TERTIARY_BG_COLOR%%"), ter); + style_sheet.replace(QStringLiteral("%%BUTTON_BG_COLOR%%"), b_bg); + style_sheet.replace(QStringLiteral("%%HOVER_BG_COLOR%%"), h_bg); + style_sheet.replace(QStringLiteral("%%FOCUS_BG_COLOR%%"), f_bg); + style_sheet.replace(QStringLiteral("%%DISABLED_TEXT_COLOR%%"), d_txt); - // Replace base theme colors (new logic) - style_sheet.replace(QStringLiteral("%%BACKGROUND_COLOR%%"), bg_color); - style_sheet.replace(QStringLiteral("%%TEXT_COLOR%%"), text_color); - style_sheet.replace(QStringLiteral("%%SECONDARY_BG_COLOR%%"), secondary_bg_color); - style_sheet.replace(QStringLiteral("%%TERTIARY_BG_COLOR%%"), tertiary_bg_color); - style_sheet.replace(QStringLiteral("%%BUTTON_BG_COLOR%%"), button_bg_color); - style_sheet.replace(QStringLiteral("%%HOVER_BG_COLOR%%"), hover_bg_color); - style_sheet.replace(QStringLiteral("%%FOCUS_BG_COLOR%%"), focus_bg_color); - style_sheet.replace(QStringLiteral("%%DISABLED_TEXT_COLOR%%"), disabled_text_color); + style_sheet += QStringLiteral( + "QSlider::handle:horizontal { background-color: %1; }" + "QCheckBox::indicator:checked { background-color: %1; border-color: %1; }" + "QToolButton { background-color: %1; color: #ffffff; border-radius: 4px; }" + ).arg(accent); setStyleSheet(style_sheet); - // This part is crucial to pass the theme to child tabs 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); + + if (is_rainbow) { + if (!rainbow_timer) { + rainbow_timer = new QTimer(this); + connect(rainbow_timer, &QTimer::timeout, this, [this] { + QString hue_hex = RainbowStyle::GetCurrentHighlightColor().name(); + QString button_css = QStringLiteral( + "QPushButton#aestheticTabButton { border: 2px solid transparent; }" + "QPushButton#aestheticTabButton:checked { color: %1; border: 2px solid %1; }" + "QPushButton#aestheticTabButton:hover { border: 2px solid %1; }" + "QPushButton#aestheticTabButton:pressed { background-color: %1; color: #ffffff; }" + ).arg(hue_hex); + + if (ui->tabButtonsContainer) ui->tabButtonsContainer->setStyleSheet(button_css); + }); + } + rainbow_timer->start(33); + } else if (rainbow_timer) { + rainbow_timer->stop(); + if (ui->tabButtonsContainer) ui->tabButtonsContainer->setStyleSheet({}); + } } void ConfigurePerGame::LoadConfiguration() { diff --git a/src/citron/configuration/configure_per_game.h b/src/citron/configuration/configure_per_game.h index b40d64a3e..8c6b82b75 100644 --- a/src/citron/configuration/configure_per_game.h +++ b/src/citron/configuration/configure_per_game.h @@ -100,9 +100,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; + QTimer* rainbow_timer{nullptr}; QButtonGroup* button_group; QPixmap map; diff --git a/src/citron/configuration/configure_ui.ui b/src/citron/configuration/configure_ui.ui index e3e2bbdef..081ea5313 100644 --- a/src/citron/configuration/configure_ui.ui +++ b/src/citron/configuration/configure_ui.ui @@ -140,7 +140,7 @@ - Enable Rainbow Mode + Enable Rainbow Tab Buttons diff --git a/src/citron/main.cpp b/src/citron/main.cpp index e1458fe59..b7d90338b 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -178,6 +178,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "citron/main.h" #include "citron/play_time_manager.h" #include "citron/startup_checks.h" +#include "citron/util/rainbow_style.h" #include "citron/uisettings.h" #ifdef CITRON_USE_AUTO_UPDATER #include "citron/updater/updater_dialog.h" @@ -6209,7 +6210,9 @@ int main(int argc, char* argv[]) { setlocale(LC_ALL, "C"); GMainWindow main_window{std::move(config), has_broken_vulkan}; - // After settings have been loaded by GMainWindow, apply the filter + + app.setStyle(new RainbowStyle(app.style())); + main_window.show(); QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, diff --git a/src/citron/util/rainbow_style.cpp b/src/citron/util/rainbow_style.cpp new file mode 100644 index 000000000..fe3adef5c --- /dev/null +++ b/src/citron/util/rainbow_style.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 citron Emulator Project + +#include "citron/util/rainbow_style.h" +#include "citron/uisettings.h" +#include "citron/theme.h" +#include +#include +#include + +float RainbowStyle::s_hue = 0.0f; + +RainbowStyle::RainbowStyle(QStyle* baseStyle) : QProxyStyle(baseStyle) { + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &RainbowStyle::UpdateHue); + m_timer->start(33); +} + +void RainbowStyle::UpdateHue() { + if (UISettings::values.enable_rainbow_mode.GetValue()) { + s_hue += 0.005f; + if (s_hue > 1.0f) s_hue = 0.0f; + } +} + +QColor RainbowStyle::GetCurrentHighlightColor() { + if (!UISettings::values.enable_rainbow_mode.GetValue()) { + return QColor(Theme::GetAccentColor()); + } + return QColor::fromHsvF(s_hue, 0.7f, 1.0f); +} + +QPalette RainbowStyle::standardPalette() const { + QPalette pal = QProxyStyle::standardPalette(); + QColor highlight = GetCurrentHighlightColor(); + pal.setColor(QPalette::Highlight, highlight); + pal.setColor(QPalette::Link, highlight); + return pal; +} diff --git a/src/citron/util/rainbow_style.h b/src/citron/util/rainbow_style.h new file mode 100644 index 000000000..f45fde2aa --- /dev/null +++ b/src/citron/util/rainbow_style.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2025 citron Emulator Project + +#pragma once + +#include +#include +#include + +class RainbowStyle : public QProxyStyle { + Q_OBJECT + +public: + explicit RainbowStyle(QStyle* baseStyle = nullptr); + + // This intercepts palette requests from every widget in the app + QPalette standardPalette() const override; + + // A helper for widgets that need the color directly + static QColor GetCurrentHighlightColor(); + +private slots: + void UpdateHue(); + +private: + QTimer* m_timer; + static float s_hue; +};