diff --git a/src/citron/CMakeLists.txt b/src/citron/CMakeLists.txt index c3b1c552d..e7b481cec 100644 --- a/src/citron/CMakeLists.txt +++ b/src/citron/CMakeLists.txt @@ -158,6 +158,8 @@ add_executable(citron configuration/shared_translation.h configuration/shared_widget.cpp configuration/shared_widget.h + configuration/style_animation_event_filter.cpp + configuration/style_animation_event_filter.h configuration/qt_config.cpp configuration/qt_config.h debugger/console.cpp diff --git a/src/citron/configuration/configure.ui b/src/citron/configuration/configure.ui index 6b300940f..d21be9c5e 100644 --- a/src/citron/configuration/configure.ui +++ b/src/citron/configuration/configure.ui @@ -1,875 +1,637 @@ - ConfigureDialog - - - - 0 - 0 - 1400 - 900 - + ConfigureDialog + + + + 0 + 0 + 1400 + 900 + + + + + 0 + 0 + + + + Qt::ApplicationModal + + + citron Configuration + + + + + + QDialog { + background-color: %%BACKGROUND_COLOR%%; + color: %%TEXT_COLOR%%; + } + + QWidget#nav_container { + background-color: %%SECONDARY_BG_COLOR%%; + border-right: 1px solid %%TERTIARY_BG_COLOR%%; + } + + QStackedWidget { + background-color: transparent; + border: none; + } + + QScrollArea { + background-color: transparent; + border: none; + } + + QPushButton.tabButton { + background-color: transparent; + color: %%TEXT_COLOR%%; + padding: 10px; + padding-left: 10px; + margin: 0px 4px; + border-radius: 6px; + font-weight: 500; + border: 2px solid transparent; + text-align: left; + } + + QPushButton.tabButton:hover { + background-color: %%HOVER_BG_COLOR%%; + } + + QPushButton.tabButton:checked { + color: %%ACCENT_COLOR%%; + font-weight: bold; + background-color: %%FOCUS_BG_COLOR%%; + border-left: 2px solid %%ACCENT_COLOR%%; + } + + /* All other styles remain the same... just paste this block at the top */ + + QWidget { + background-color: %%BACKGROUND_COLOR%%; + color: %%TEXT_COLOR%%; + } + + QScrollBar:vertical { + background-color: %%SECONDARY_BG_COLOR%%; + width: 14px; + border-radius: 7px; + margin: 2px; + } + + QScrollBar::handle:vertical { + background-color: %%TERTIARY_BG_COLOR%%; + 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: %%SECONDARY_BG_COLOR%%; + height: 14px; + border-radius: 7px; + margin: 2px; + } + + QScrollBar::handle:horizontal { + background-color: %%TERTIARY_BG_COLOR%%; + 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:pressed { + background-color: %%ACCENT_COLOR_PRESSED%%; + } + + QGroupBox { + font-weight: bold; + border: 1px solid %%SECONDARY_BG_COLOR%%; + border-radius: 8px; + margin-top: 12px; + padding-top: 12px; + background-color: %%BACKGROUND_COLOR%%; + color: %%TEXT_COLOR%%; + } + + QGroupBox::title { + subcontrol-origin: margin; + left: 12px; + padding: 0 8px 0 8px; + color: %%TEXT_COLOR%%; + font-weight: bold; + } + + QCheckBox { + color: %%TEXT_COLOR%%; + spacing: 10px; + padding: 4px; + background-color: transparent; + } + + QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid %%TERTIARY_BG_COLOR%%; + border-radius: 4px; + background-color: %%SECONDARY_BG_COLOR%%; + } + + QCheckBox::indicator:checked { + background-color: %%ACCENT_COLOR%%; + border-color: %%ACCENT_COLOR%%; + } + + QCheckBox::indicator:hover { + border-color: %%ACCENT_COLOR%%; + } + + QComboBox { + background-color: %%SECONDARY_BG_COLOR%%; + border: 1px solid %%TERTIARY_BG_COLOR%%; + border-radius: 6px; + padding: 8px 12px; + color: %%TEXT_COLOR%%; + min-width: 120px; + min-height: 28px; + selection-background-color: %%ACCENT_COLOR%%; + } + + QComboBox:hover { + border-color: %%ACCENT_COLOR%%; + background-color: %%HOVER_BG_COLOR%%; + } + + QComboBox:focus { + border-color: %%ACCENT_COLOR%%; + background-color: %%FOCUS_BG_COLOR%%; + } + + QComboBox::drop-down { + border: none; + width: 25px; + subcontrol-origin: padding; + subcontrol-position: top right; + background-color: transparent; + } + + QComboBox QAbstractItemView { + background-color: %%SECONDARY_BG_COLOR%%; + border: 1px solid %%ACCENT_COLOR%%; + selection-background-color: %%ACCENT_COLOR%%; + color: %%TEXT_COLOR%%; + outline: none; + } + + QLineEdit { + background-color: %%SECONDARY_BG_COLOR%%; + border: 1px solid %%TERTIARY_BG_COLOR%%; + border-radius: 6px; + padding: 8px 12px; + color: %%TEXT_COLOR%%; + min-height: 20px; + selection-background-color: %%ACCENT_COLOR%%; + } + + 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%%; + } + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + 220 + + + 220 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true - - - 0 - 0 - - - - Qt::ApplicationModal - - - citron Configuration - - - - - - QDialog { - background-color: %%BACKGROUND_COLOR%%; - color: %%TEXT_COLOR%%; - } - - QWidget { - background-color: %%BACKGROUND_COLOR%%; - color: %%TEXT_COLOR%%; - } - - QStackedWidget { - background-color: %%BACKGROUND_COLOR%%; - border: 1px solid %%SECONDARY_BG_COLOR%%; - border-radius: 8px; - margin: 0px; - padding: 0px; - } - - QScrollArea { - background-color: %%BACKGROUND_COLOR%%; - border: none; - border-radius: 8px; - } - - QScrollArea > QWidget > QWidget { - background-color: %%BACKGROUND_COLOR%%; - } - - QScrollBar:vertical { - background-color: %%SECONDARY_BG_COLOR%%; - width: 14px; - border-radius: 7px; - margin: 2px; - } - - QScrollBar::handle:vertical { - background-color: %%TERTIARY_BG_COLOR%%; - 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: %%SECONDARY_BG_COLOR%%; - height: 14px; - border-radius: 7px; - margin: 2px; - } - - QScrollBar::handle:horizontal { - background-color: %%TERTIARY_BG_COLOR%%; - 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: %%BUTTON_BG_COLOR%%; - color: %%TEXT_COLOR%%; - 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 %%SECONDARY_BG_COLOR%%; - 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: %%HOVER_BG_COLOR%%; - border-color: %%TERTIARY_BG_COLOR%%; - } - - QPushButton.tabButton:pressed { - background-color: %%ACCENT_COLOR_PRESSED%%; - } - - QTabWidget { - background-color: %%BACKGROUND_COLOR%%; - border: none; - } - - QTabWidget::pane { - border: 1px solid %%SECONDARY_BG_COLOR%%; - background-color: %%BACKGROUND_COLOR%%; - border-radius: 8px; - margin: 0px; - padding: 0px; - } - - QTabWidget::tab-bar { - alignment: left; - } - - QTabBar { - background-color: %%BACKGROUND_COLOR%%; - border: none; - } - - QTabBar::tab { - background-color: %%BUTTON_BG_COLOR%%; - color: %%TEXT_COLOR%%; - padding: 12px 20px; - margin-right: 2px; - margin-bottom: 2px; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - min-width: 100px; - font-weight: 500; - border: 1px solid %%SECONDARY_BG_COLOR%%; - 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: %%HOVER_BG_COLOR%%; - border-color: %%TERTIARY_BG_COLOR%%; - } - - QTabBar QToolButton { - background-color: %%BUTTON_BG_COLOR%%; - border: 1px solid %%SECONDARY_BG_COLOR%%; - border-radius: 4px; - padding: 4px; - margin: 2px; - } - - QTabBar QToolButton:hover { - background-color: %%HOVER_BG_COLOR%%; - border-color: %%ACCENT_COLOR%%; - } - - QTabBar::scroller { - width: 30px; - } - - QGroupBox { - font-weight: bold; - border: 1px solid %%SECONDARY_BG_COLOR%%; - border-radius: 8px; - margin-top: 12px; - padding-top: 12px; - background-color: %%BACKGROUND_COLOR%%; - color: %%TEXT_COLOR%%; - } - - QGroupBox::title { - subcontrol-origin: margin; - left: 12px; - padding: 0 8px 0 8px; - color: %%TEXT_COLOR%%; - font-weight: bold; - } - - QCheckBox { - color: %%TEXT_COLOR%%; - spacing: 10px; - padding: 4px; - background-color: transparent; - } - - QCheckBox::indicator { - width: 18px; - height: 18px; - border: 2px solid %%TERTIARY_BG_COLOR%%; - border-radius: 4px; - background-color: %%SECONDARY_BG_COLOR%%; - } - - QCheckBox::indicator:checked { - background-color: %%ACCENT_COLOR%%; - border-color: %%ACCENT_COLOR%%; - } - - QCheckBox::indicator:hover { - border-color: %%ACCENT_COLOR%%; - } - - QComboBox { - background-color: %%SECONDARY_BG_COLOR%%; - border: 1px solid %%TERTIARY_BG_COLOR%%; - border-radius: 6px; - padding: 8px 12px; - color: %%TEXT_COLOR%%; - min-width: 120px; - min-height: 28px; - selection-background-color: %%ACCENT_COLOR%%; - } - - QComboBox:hover { - border-color: %%ACCENT_COLOR%%; - background-color: %%HOVER_BG_COLOR%%; - } - - QComboBox:focus { - border-color: %%ACCENT_COLOR%%; - background-color: %%FOCUS_BG_COLOR%%; - } - - QComboBox::drop-down { - border: none; - width: 25px; - subcontrol-origin: padding; - subcontrol-position: top right; - background-color: transparent; - } - - - QComboBox QAbstractItemView { - background-color: %%SECONDARY_BG_COLOR%%; - border: 1px solid %%ACCENT_COLOR%%; - selection-background-color: %%ACCENT_COLOR%%; - color: %%TEXT_COLOR%%; - 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: %%SECONDARY_BG_COLOR%%; - border: 1px solid %%TERTIARY_BG_COLOR%%; - border-radius: 6px; - padding: 8px 12px; - color: %%TEXT_COLOR%%; - min-height: 20px; - selection-background-color: %%ACCENT_COLOR%%; - } - - QLineEdit:focus { - border-color: %%ACCENT_COLOR%%; - background-color: %%FOCUS_BG_COLOR%%; - } - - 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: %%TERTIARY_BG_COLOR%%; - color: %%DISABLED_TEXT_COLOR%%; - } - - 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: %%TEXT_COLOR%%; - background-color: transparent; - padding: 2px; - } - - QListWidget { - background-color: %%SECONDARY_BG_COLOR%%; - border: 1px solid %%TERTIARY_BG_COLOR%%; - border-radius: 6px; - color: %%TEXT_COLOR%%; - 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: %%HOVER_BG_COLOR%%; - } - - QSlider::groove:horizontal { - border: 1px solid %%TERTIARY_BG_COLOR%%; - height: 8px; - background-color: %%SECONDARY_BG_COLOR%%; - 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: %%SECONDARY_BG_COLOR%%; - border: 1px solid %%TERTIARY_BG_COLOR%%; - border-radius: 6px; - padding: 6px; - color: %%TEXT_COLOR%%; - min-height: 20px; - } - - QSpinBox:focus, QDoubleSpinBox:focus { - border-color: %%ACCENT_COLOR%%; - background-color: %%FOCUS_BG_COLOR%%; - } - - QRadioButton { - color: %%TEXT_COLOR%%; - spacing: 8px; - padding: 4px; - } - - QRadioButton::indicator { - width: 16px; - height: 16px; - border: 2px solid %%TERTIARY_BG_COLOR%%; - border-radius: 8px; - background-color: %%SECONDARY_BG_COLOR%%; - } - - QRadioButton::indicator:checked { - background-color: %%ACCENT_COLOR%%; - border-color: %%ACCENT_COLOR%%; - } - - QRadioButton::indicator:hover { - border-color: %%ACCENT_COLOR%%; - } - - /* High DPI specific styles */ - @media (min-resolution: 192dpi) { - QPushButton.tabButton { - padding: 12px 16px; - min-width: 90px; - max-width: 170px; - font-size: 14px; - } - - QComboBox { - min-width: 140px; - min-height: 32px; - padding: 10px 14px; - } - - QLineEdit { - min-height: 24px; - padding: 10px 14px; - } - - QPushButton { - min-height: 24px; - padding: 12px 24px; - } - - QCheckBox::indicator { - width: 20px; - height: 20px; - } - - QRadioButton::indicator { - width: 18px; - height: 18px; - } - } - - @media (min-resolution: 240dpi) { - QPushButton.tabButton { - padding: 14px 18px; - min-width: 95px; - max-width: 180px; - font-size: 16px; - } - - QComboBox { - min-width: 160px; - min-height: 36px; - padding: 12px 16px; - } - - QLineEdit { - min-height: 28px; - padding: 12px 16px; - } - - QPushButton { - min-height: 28px; - padding: 14px 28px; - } - - QCheckBox::indicator { - width: 22px; - height: 22px; - } - - QRadioButton::indicator { - width: 20px; - height: 20px; - } - } - - - - - 8 + + + + 0 + 0 + 220 + 856 + + + + + + + General - - 12 + + true - - 12 + + true - - 12 + + tabButton - - 12 + + + + + + UI - - - - 8 - - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAsNeeded - - - true - - - - - 0 - 0 - 1376 - 51 - - - - - 4 - - - - - General - - - true - - - true - - - tabButton - - - - - - - UI - - - true - - - tabButton - - - - - - - System - - - true - - - tabButton - - - - - - - CPU - - - true - - - tabButton - - - - - - - Graphics - - - true - - - tabButton - - - - - - - Graphics (Adv) - - - true - - - tabButton - - - - - - - Audio - - - true - - - tabButton - - - - - - - Input - - - true - - - tabButton - - - - - - - Hotkeys - - - true - - - tabButton - - - - - - - Network - - - true - - - tabButton - - - - - - - Web - - - true - - - tabButton - - - - - - - Filesystem - - - true - - - tabButton - - - - - - - Profiles - - - true - - - tabButton - - - - - - - Applets - - - true - - - tabButton - - - - - - - Logging - - - true - - - tabButton - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - true - - - - - 0 - 0 - 1376 - 745 - - - - - - - - 0 - 0 - - - - 0 - - - - - - - - - - - - - 12 - - - 8 - - - 8 - - - 8 - - - 0 - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - + + true + + + tabButton + + + + + + + System + + + true + + + tabButton + + + + + + + CPU + + + true + + + tabButton + + + + + + + Graphics + + + true + + + tabButton + + + + + + + Graphics (Adv) + + + true + + + tabButton + + + + + + + Audio + + + true + + + tabButton + + + + + + + Input + + + true + + + tabButton + + + + + + + Hotkeys + + + true + + + tabButton + + + + + + + Network + + + true + + + tabButton + + + + + + + Web + + + true + + + tabButton + + + + + + + Filesystem + + + true + + + tabButton + + + + + + + Profiles + + + true + + + tabButton + + + + + + + Applets + + + true + + + tabButton + + + + + + + Logging + + + true + + + tabButton + + + + + + + + - - - - buttonBox - accepted() - ConfigureDialog - accept() - - - 20 - 20 - - - 20 - 20 - - - - - buttonBox - rejected() - ConfigureDialog - reject() - - - 20 - 20 - - - 20 - 20 - - - - + + + + + 8 + + + 12 + + + 12 + + + 12 + + + 12 + + + + + + 16777215 + 55 + + + + true + + + + + 0 + 0 + 1168 + 55 + + + + + + + + + + true + + + + + 0 + 0 + 1168 + 777 + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + buttonBox + accepted() + ConfigureDialog + accept() + + + 20 + 20 + + + 20 + 20 + + + + + buttonBox + rejected() + ConfigureDialog + reject() + + + 20 + 20 + + + 20 + 20 + + + + diff --git a/src/citron/configuration/configure_dialog.cpp b/src/citron/configuration/configure_dialog.cpp index 545c8f878..3e60d1cae 100644 --- a/src/citron/configuration/configure_dialog.cpp +++ b/src/citron/configuration/configure_dialog.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include "common/logging/log.h" #include "common/settings.h" #include "common/settings_enums.h" @@ -38,89 +38,29 @@ #include "citron/configuration/configure_system.h" #include "citron/configuration/configure_ui.h" #include "citron/configuration/configure_web.h" +#include "citron/configuration/style_animation_event_filter.h" #include "citron/hotkeys.h" #include "citron/theme.h" #include "citron/uisettings.h" -// Event filter class to forward wheel events to scroll area's scrollbar -class ScrollAreaWheelEventFilter : public QObject { -public: - explicit ScrollAreaWheelEventFilter(QScrollArea* scroll_area, QObject* parent = nullptr, - bool prefer_horizontal = false) - : QObject(parent), scroll_area_(scroll_area), prefer_horizontal_(prefer_horizontal) {} - -protected: - bool eventFilter(QObject* obj, QEvent* event) override { - if (event->type() == QEvent::Wheel && scroll_area_) { - auto* wheel_event = static_cast(event); - const QPoint angle_delta = wheel_event->angleDelta(); - - // Determine which scrollbar to use based on scroll direction and preference - bool use_horizontal = prefer_horizontal_ || (std::abs(angle_delta.x()) > std::abs(angle_delta.y())); - - if (use_horizontal) { - // Try horizontal scrolling first - if (scroll_area_->horizontalScrollBar()->maximum() > 0) { - QApplication::sendEvent(scroll_area_->horizontalScrollBar(), wheel_event); - return true; - } - } - - // Try vertical scrolling - if (scroll_area_->verticalScrollBar()->maximum() > 0) { - QApplication::sendEvent(scroll_area_->verticalScrollBar(), wheel_event); - return true; - } - - // If vertical didn't work and we didn't try horizontal, try it now - if (!use_horizontal && scroll_area_->horizontalScrollBar()->maximum() > 0) { - QApplication::sendEvent(scroll_area_->horizontalScrollBar(), wheel_event); - return true; - } - } - return QObject::eventFilter(obj, event); - } - -private: - QScrollArea* scroll_area_; - bool prefer_horizontal_; -}; - static QScrollArea* CreateScrollArea(QWidget* widget) { auto* scroll_area = new QScrollArea(); scroll_area->setWidget(widget); scroll_area->setWidgetResizable(true); scroll_area->setFrameShape(QFrame::NoFrame); - scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - - // Install event filter on the widget to forward wheel events to scrollbar - auto* filter = new ScrollAreaWheelEventFilter(scroll_area, scroll_area); - widget->installEventFilter(filter); - return scroll_area; } -// Helper function to detect if the application should be in a dark theme state static bool IsDarkMode() { const std::string& theme_name = UISettings::values.theme; - - // Priority 1: Check for explicitly chosen dark themes. if (theme_name == "qdarkstyle" || theme_name == "colorful_dark" || theme_name == "qdarkstyle_midnight_blue" || theme_name == "colorful_midnight_blue") { return true; } - - // Priority 2: Check for adaptive themes ("default" and "colorful"). - // For these, we fall back to checking the OS palette. if (theme_name == "default" || theme_name == "colorful") { - const QPalette palette = qApp->palette(); - const QColor text_color = palette.color(QPalette::WindowText); - const QColor base_color = palette.color(QPalette::Window); - return text_color.value() > base_color.value(); + return qApp->palette().color(QPalette::WindowText).value() > + qApp->palette().color(QPalette::Window).value(); } - - // Fallback for any other unknown theme (assumed light). return false; } @@ -128,61 +68,60 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, InputCommon::InputSubsystem* input_subsystem, std::vector& vk_device_records, Core::System& system_, bool enable_web_config) -: QDialog(parent), ui{std::make_unique()}, -registry(registry_), system{system_}, builder{std::make_unique( - this, !system_.IsPoweredOn())}, - applets_tab{std::make_unique(system_, nullptr, *builder, this)}, - audio_tab{std::make_unique(system_, nullptr, *builder, this)}, - cpu_tab{std::make_unique(system_, nullptr, *builder, this)}, - debug_tab_tab{std::make_unique(system_, this)}, - filesystem_tab{std::make_unique(this)}, - general_tab{std::make_unique(system_, nullptr, *builder, this)}, - graphics_advanced_tab{ - std::make_unique(system_, nullptr, *builder, this)}, - ui_tab{std::make_unique(system_, this)}, - graphics_tab{std::make_unique( - system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, - [this](Settings::AspectRatio ratio, Settings::ResolutionSetup setup) { - ui_tab->UpdateScreenshotInfo(ratio, setup); - }, - nullptr, *builder, this)}, -hotkeys_tab{std::make_unique(system_.HIDCore(), this)}, -input_tab{std::make_unique(system_, this)}, -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)} { + : QDialog(parent), ui{std::make_unique()}, registry(registry_), + system{system_}, + builder{std::make_unique(this, !system_.IsPoweredOn())}, + applets_tab{std::make_unique(system_, nullptr, *builder, this)}, + audio_tab{std::make_unique(system_, nullptr, *builder, this)}, + cpu_tab{std::make_unique(system_, nullptr, *builder, this)}, + debug_tab_tab{std::make_unique(system_, this)}, + filesystem_tab{std::make_unique(this)}, + general_tab{std::make_unique(system_, nullptr, *builder, this)}, + graphics_advanced_tab{ + std::make_unique(system_, nullptr, *builder, this)}, + ui_tab{std::make_unique(system_, this)}, + graphics_tab{std::make_unique( + system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, + [this](Settings::AspectRatio ratio, Settings::ResolutionSetup setup) { + ui_tab->UpdateScreenshotInfo(ratio, setup); + }, + nullptr, *builder, this)}, + hotkeys_tab{std::make_unique(system_.HIDCore(), this)}, + input_tab{std::make_unique(system_, this)}, + 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)} { Settings::SetConfiguringGlobal(true); - setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | - Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); - - setAttribute(Qt::WA_TranslucentBackground, false); - setAttribute(Qt::WA_NoSystemBackground, false); - setAttribute(Qt::WA_DontShowOnScreen, false); - + Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); ui->setupUi(this); - // Enable wheel event scrolling on the top button scroll area (horizontal scrolling) - auto* top_button_filter = new ScrollAreaWheelEventFilter(ui->topButtonScrollArea, this, true); - ui->topButtonScrollArea->widget()->installEventFilter(top_button_filter); - ui->topButtonScrollArea->installEventFilter(top_button_filter); + auto* animation_filter = new StyleAnimationEventFilter(this); + const auto button_qlist = ui->topButtonWidget->findChildren(); + tab_buttons = std::vector(button_qlist.begin(), button_qlist.end()); + auto* nav_layout = new QVBoxLayout(); + nav_layout->setContentsMargins(8, 8, 8, 8); + nav_layout->setSpacing(4); + for (QPushButton* button : tab_buttons) { + button->setParent(ui->topButtonWidget); + // Buttons are added to a layout in SetUIPositioning + if (button->property("class").toString() == QStringLiteral("tabButton")) { + button->installEventFilter(animation_filter); + } + } + delete ui->topButtonWidget->layout(); + ui->topButtonWidget->setLayout(nav_layout); last_palette_text_color = qApp->palette().color(QPalette::WindowText); - if (!UISettings::values.configure_dialog_geometry.isEmpty()) { restoreGeometry(UISettings::values.configure_dialog_geometry); } - UpdateTheme(); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - tab_button_group = std::make_unique(this); tab_button_group->setExclusive(true); - tab_button_group->addButton(ui->generalTabButton, 0); tab_button_group->addButton(ui->uiTabButton, 1); tab_button_group->addButton(ui->systemTabButton, 2); @@ -217,36 +156,25 @@ rainbow_timer{new QTimer(this)} { connect(tab_button_group.get(), qOverload(&QButtonGroup::idClicked), this, [this](int id) { ui->stackedWidget->setCurrentIndex(id); - if (id == 14) { - debug_tab_tab->SetCurrentIndex(0); - } }); - 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); - general_tab->SetResetCallback([&] { this->close(); }); - SetConfiguration(); - connect(ui_tab.get(), &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged); - if (system.IsPoweredOn()) { - QPushButton* apply_button = ui->buttonBox->button(QDialogButtonBox::Apply); - if (apply_button) { - connect(apply_button, &QAbstractButton::clicked, this, - &ConfigureDialog::HandleApplyButtonClicked); + if (auto* apply_button = ui->buttonBox->button(QDialogButtonBox::Apply)) { + connect(apply_button, &QAbstractButton::clicked, this, &ConfigureDialog::HandleApplyButtonClicked); } } - ui->stackedWidget->setCurrentIndex(0); ui->generalTabButton->setChecked(true); - ui->buttonBox->setFocus(); + + SetUIPositioning(QString::fromStdString(UISettings::values.ui_positioning.GetValue())); } ConfigureDialog::~ConfigureDialog() { @@ -257,25 +185,17 @@ 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; - } + 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); - } + if (!rainbow_timer->isActive()) rainbow_timer->start(150); } else { - if (rainbow_timer->isActive()) { - rainbow_timer->stop(); - } + 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_dark = IsDarkMode(); const QString bg_color = is_dark ? QStringLiteral("#2b2b2b") : QStringLiteral("#ffffff"); const QString text_color = is_dark ? QStringLiteral("#ffffff") : QStringLiteral("#000000"); @@ -285,20 +205,14 @@ void ConfigureDialog::UpdateTheme() { 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; - - // 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); - - // 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); @@ -307,10 +221,7 @@ void ConfigureDialog::UpdateTheme() { 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); - 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); @@ -318,6 +229,50 @@ void ConfigureDialog::UpdateTheme() { graphics_advanced_tab->SetTemplateStyleSheet(style_sheet); } +void ConfigureDialog::SetUIPositioning(const QString& positioning) { + auto* v_layout = qobject_cast(ui->topButtonWidget->layout()); + auto* h_layout = qobject_cast(ui->horizontalNavWidget->layout()); + + if (!v_layout || !h_layout) { + LOG_ERROR(Frontend, "Could not find navigation layouts to rearrange"); + return; + } + + if (positioning == QStringLiteral("Horizontal")) { + ui->nav_container->hide(); + ui->horizontalNavScrollArea->show(); + // Remove stretch from vertical layout if it exists + if (v_layout->count() > 0) { + if (auto* item = v_layout->itemAt(v_layout->count() - 1); item && item->spacerItem()) { + v_layout->takeAt(v_layout->count() - 1); + delete item; + } + } + for (QPushButton* button : tab_buttons) { + v_layout->removeWidget(button); + h_layout->addWidget(button); + button->setStyleSheet(QStringLiteral("text-align: center;")); + } + h_layout->addStretch(1); + } else { // Vertical + ui->horizontalNavScrollArea->hide(); + ui->nav_container->show(); + // Remove stretch from horizontal layout if it exists + if (h_layout->count() > 0) { + if (auto* item = h_layout->itemAt(h_layout->count() - 1); item && item->spacerItem()) { + h_layout->takeAt(h_layout->count() - 1); + delete item; + } + } + for (QPushButton* button : tab_buttons) { + h_layout->removeWidget(button); + v_layout->addWidget(button); + button->setStyleSheet(QStringLiteral("")); // Reset to parent stylesheet + } + v_layout->addStretch(1); + } +} + void ConfigureDialog::SetConfiguration() {} void ConfigureDialog::ApplyConfiguration() { @@ -344,23 +299,18 @@ void ConfigureDialog::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); } - if (event->type() == QEvent::PaletteChange) { - const QColor current_color = qApp->palette().color(QPalette::WindowText); - if (current_color != last_palette_text_color) { - last_palette_text_color = current_color; + if (qApp->palette().color(QPalette::WindowText) != last_palette_text_color) { + last_palette_text_color = qApp->palette().color(QPalette::WindowText); UpdateTheme(); } } - QDialog::changeEvent(event); } void ConfigureDialog::RetranslateUI() { const int old_index = ui->stackedWidget->currentIndex(); - ui->retranslateUi(this); - SetConfiguration(); ui->stackedWidget->setCurrentIndex(old_index); } diff --git a/src/citron/configuration/configure_dialog.h b/src/citron/configuration/configure_dialog.h index 409dab386..45bbd0073 100644 --- a/src/citron/configuration/configure_dialog.h +++ b/src/citron/configuration/configure_dialog.h @@ -9,24 +9,17 @@ #include #include #include "common/settings_enums.h" -#include "citron/configuration/shared_widget.h" // <-- Correct header for Builder +#include "citron/configuration/shared_widget.h" -// Forward declarations for other types +// Forward declarations class HotkeyRegistry; class QButtonGroup; +class QPushButton; class QTimer; -namespace InputCommon { - class InputSubsystem; -} -namespace Core { - class System; -} -namespace VkDeviceInfo { - class Record; -} -namespace Ui { - class ConfigureDialog; -} +namespace InputCommon { class InputSubsystem; } +namespace Core { class System; } +namespace VkDeviceInfo { class Record; } +namespace Ui { class ConfigureDialog; } class ConfigureApplets; class ConfigureAudio; class ConfigureCpu; @@ -62,18 +55,17 @@ public slots: signals: void LanguageChanged(const QString& locale); +private slots: + void SetUIPositioning(const QString& positioning); + private: void SetConfiguration(); void HandleApplyButtonClicked(); - void changeEvent(QEvent* event) override; void RetranslateUI(); - void OnLanguageChanged(const QString& locale); QColor last_palette_text_color; - - // All members are now in the EXACT correct order to match the constructor std::unique_ptr ui; HotkeyRegistry& registry; Core::System& system; @@ -94,6 +86,7 @@ private: std::unique_ptr system_tab; std::unique_ptr web_tab; std::unique_ptr tab_button_group; + std::vector tab_buttons; QTimer* rainbow_timer; float rainbow_hue = 0.0f; }; diff --git a/src/citron/configuration/configure_ui.cpp b/src/citron/configuration/configure_ui.cpp index da8e6ba85..3ab3673ca 100644 --- a/src/citron/configuration/configure_ui.cpp +++ b/src/citron/configuration/configure_ui.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -115,6 +117,19 @@ resolution_setting{Settings::values.resolution_setup.GetValue()}, system{system_ QString::fromUtf8(theme.second)); } + ui_positioning_combo = new QComboBox(this); + ui_positioning_combo->addItem(tr("Vertical"), QStringLiteral("Vertical")); + ui_positioning_combo->addItem(tr("Horizontal"), QStringLiteral("Horizontal")); + if (auto* layout = qobject_cast(ui->theme_combobox->parentWidget()->layout())) { + layout->addRow(tr("UI Positioning"), ui_positioning_combo); + } else if (auto* group_layout = qobject_cast(ui->theme_combobox->parentWidget()->layout())) { + group_layout->addWidget(ui_positioning_combo); + } + + connect(ui_positioning_combo, &QComboBox::currentTextChanged, this, [this](const QString& text){ + emit UIPositioningChanged(text); + }); + InitializeIconSizeComboBox(); InitializeRowComboBoxes(); @@ -174,6 +189,7 @@ ConfigureUi::~ConfigureUi() = default; void ConfigureUi::ApplyConfiguration() { UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString().toStdString(); + UISettings::values.ui_positioning = ui_positioning_combo->currentData().toString().toStdString(); UISettings::values.enable_rainbow_mode = ui->rainbowModeCheckBox->isChecked(); UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); UISettings::values.show_compat = ui->show_compat->isChecked(); @@ -205,6 +221,8 @@ void ConfigureUi::SetConfiguration() { ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme))); ui->language_combobox->setCurrentIndex(ui->language_combobox->findData( QString::fromStdString(UISettings::values.language.GetValue()))); + ui_positioning_combo->setCurrentIndex(ui_positioning_combo->findData( + QString::fromStdString(UISettings::values.ui_positioning.GetValue()))); ui->rainbowModeCheckBox->setChecked(UISettings::values.enable_rainbow_mode.GetValue()); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); @@ -252,6 +270,11 @@ void ConfigureUi::changeEvent(QEvent* event) { void ConfigureUi::RetranslateUI() { ui->retranslateUi(this); + const int pos_index = ui_positioning_combo->currentIndex(); + ui_positioning_combo->setItemText(0, tr("Vertical")); + ui_positioning_combo->setItemText(1, tr("Horizontal")); + ui_positioning_combo->setCurrentIndex(pos_index); + for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) { ui->game_icon_size_combobox->setItemText(i, GetTranslatedGameIconSize(static_cast(i))); diff --git a/src/citron/configuration/configure_ui.h b/src/citron/configuration/configure_ui.h index a66c12cb9..6f2b4e2cc 100644 --- a/src/citron/configuration/configure_ui.h +++ b/src/citron/configuration/configure_ui.h @@ -8,6 +8,8 @@ #include #include "common/settings_enums.h" +class QComboBox; + namespace Core { class System; } @@ -31,6 +33,7 @@ public: signals: void LanguageChanged(const QString& locale); void themeChanged(); + void UIPositioningChanged(const QString& positioning); private slots: void OnLanguageChanged(int index); @@ -54,6 +57,7 @@ private: void UpdateWidthText(); std::unique_ptr ui; + QComboBox* ui_positioning_combo; Settings::AspectRatio ratio; Settings::ResolutionSetup resolution_setting; diff --git a/src/citron/configuration/style_animation_event_filter.cpp b/src/citron/configuration/style_animation_event_filter.cpp new file mode 100644 index 000000000..eafb74641 --- /dev/null +++ b/src/citron/configuration/style_animation_event_filter.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "citron/configuration/style_animation_event_filter.h" +#include +#include +#include + +StyleAnimationEventFilter::StyleAnimationEventFilter(QObject* parent) : QObject(parent) {} + +bool StyleAnimationEventFilter::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::Enter || event->type() == QEvent::Leave) { + if (auto* widget = qobject_cast(obj)) { + // Trigger style update for hover effects + widget->update(); + } + } + return QObject::eventFilter(obj, event); +} diff --git a/src/citron/configuration/style_animation_event_filter.h b/src/citron/configuration/style_animation_event_filter.h new file mode 100644 index 000000000..1502d96ff --- /dev/null +++ b/src/citron/configuration/style_animation_event_filter.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-FileCopyrightText: 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +class StyleAnimationEventFilter : public QObject { + Q_OBJECT + +public: + explicit StyleAnimationEventFilter(QObject* parent = nullptr); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; +}; diff --git a/src/citron/uisettings.h b/src/citron/uisettings.h index 682339793..a375f9b43 100644 --- a/src/citron/uisettings.h +++ b/src/citron/uisettings.h @@ -169,6 +169,7 @@ namespace UISettings { Setting language{linkage, {}, "language", Category::Paths}; std::string theme; + Setting ui_positioning{linkage, "Vertical", "ui_positioning", Category::Ui}; Setting accent_color{linkage, "#4a9eff", "accent_color", Category::Ui}; Setting enable_rainbow_mode{linkage, false, "enable_rainbow_mode", Category::Ui};