From 3f5587ea9be1ae2967dd42e1147dd0dfcd2b0335 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Fri, 21 Nov 2025 18:55:43 +1000 Subject: [PATCH] Add UI positioning option for configuration dialog Allows users to switch between vertical and horizontal navigation layouts. Includes new UI positioning setting, dynamic layout switching, and style animation event filter for improved UX. Signed-off-by: Zephyron --- src/citron/CMakeLists.txt | 2 + src/citron/configuration/configure.ui | 1492 +++++++---------- src/citron/configuration/configure_dialog.cpp | 246 ++- src/citron/configuration/configure_dialog.h | 29 +- src/citron/configuration/configure_ui.cpp | 23 + src/citron/configuration/configure_ui.h | 4 + .../style_animation_event_filter.cpp | 20 + .../style_animation_event_filter.h | 17 + src/citron/uisettings.h | 1 + 9 files changed, 803 insertions(+), 1031 deletions(-) create mode 100644 src/citron/configuration/style_animation_event_filter.cpp create mode 100644 src/citron/configuration/style_animation_event_filter.h 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};