From 4b94557b6ab7b150e9ed3c6a830ec7efe73c9862 Mon Sep 17 00:00:00 2001 From: Sightem Date: Sun, 12 Oct 2025 17:40:19 -0700 Subject: [PATCH 1/3] initial implementation of runtime theme switching --- gui/qt/mainwindow.cpp | 119 +++++++++++++++++++++++++++++++++++++----- gui/qt/mainwindow.h | 17 ++++++ gui/qt/mainwindow.ui | 39 ++++++++++++++ gui/qt/settings.cpp | 2 + 4 files changed, 165 insertions(+), 12 deletions(-) diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index 50d6f556..f6a3c442 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -37,11 +39,40 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include +namespace { +QPalette createFusionDarkPalette(const QPalette &base) { + QPalette palette = base; + palette.setColor(QPalette::Window, QColor(53, 53, 53)); + palette.setColor(QPalette::WindowText, Qt::white); + palette.setColor(QPalette::Base, QColor(25, 25, 25)); + palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + palette.setColor(QPalette::ToolTipBase, Qt::white); + palette.setColor(QPalette::ToolTipText, Qt::white); + palette.setColor(QPalette::Text, Qt::white); + palette.setColor(QPalette::Button, QColor(53, 53, 53)); + palette.setColor(QPalette::ButtonText, Qt::white); + palette.setColor(QPalette::BrightText, Qt::red); + palette.setColor(QPalette::Link, QColor(42, 130, 218)); + palette.setColor(QPalette::Highlight, QColor(76, 163, 224)); + palette.setColor(QPalette::HighlightedText, Qt::black); + + static constexpr QColor disabledColor(127, 127, 127); + palette.setColor(QPalette::Disabled, QPalette::Text, disabledColor); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor); + palette.setColor(QPalette::PlaceholderText, disabledColor); + return palette; +} +} + #ifdef Q_OS_MACOS # include "os/mac/kdmactouchbar.h" #endif @@ -71,8 +102,30 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U ui->setupUi(this); - m_styleForMode[0] = m_styleForMode[1] = QApplication::style()->name(); - darkModeSwitch(isSystemInDarkMode()); + m_styleForMode[0] = QApplication::style()->objectName(); + if (m_styleForMode[0].isEmpty()) { + m_styleForMode[0] = QApplication::style()->name(); + } + if (m_styleForMode[0].isEmpty()) { + m_styleForMode[0] = QStringLiteral("fusion"); + } + m_styleForMode[1] = QStringLiteral("fusion"); + + if (const QStyle *fusionStyle = QStyleFactory::create(QStringLiteral("Fusion"))) { + m_fusionLightPalette = fusionStyle->standardPalette(); + delete fusionStyle; + } else { + m_fusionLightPalette = QApplication::style()->standardPalette(); + } + m_fusionDarkPalette = createFusionDarkPalette(m_fusionLightPalette); + + { + QSignalBlocker blocker(ui->comboTheme); + ui->comboTheme->setCurrentIndex(static_cast(m_themePreference)); + } + connect(ui->comboTheme, &QComboBox::currentIndexChanged, this, &MainWindow::setThemePreference); + + applyThemeFromPreference(); setStyleSheet(QStringLiteral("QMainWindow::separator{ width: 0px; height: 0px; }")); @@ -598,6 +651,7 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U } setAutoUpdates(m_config->value(SETTING_AUTOUPDATE, CEMU_RELEASE).toBool()); + setThemePreference(m_config->value(SETTING_UI_THEME, static_cast(ThemePreference::System)).toInt()); checkVersion(); #ifdef Q_OS_WIN @@ -1130,14 +1184,40 @@ void MainWindow::translateExtras(int init) { } } -void MainWindow::darkModeSwitch(bool darkMode) { - QApplication::setStyle(m_styleForMode[darkMode]); - if (darkMode != isRunningInDarkMode()) { - m_styleForMode[darkMode] = QStringLiteral("fusion"); - QApplication::setStyle(m_styleForMode[darkMode]); - darkMode = isRunningInDarkMode(); +static void repolishAfterThemeChanged() { + const auto widgets = QApplication::allWidgets(); + for (QWidget *w : widgets) { + if (QStyle *st = w->style()) { + st->unpolish(w); + st->polish(w); + } + w->update(); + } +} + +void MainWindow::applyThemeFromPreference() { + switch (m_themePreference) { + case ThemePreference::System: + QApplication::setStyle(m_styleForMode[0]); + QApplication::setPalette(QApplication::style()->standardPalette()); + break; + case ThemePreference::Light: + QApplication::setStyle(m_styleForMode[1]); + QApplication::setPalette(m_fusionLightPalette); + break; + case ThemePreference::Dark: + QApplication::setStyle(m_styleForMode[1]); + QApplication::setPalette(m_fusionDarkPalette); + break; } + const bool dark = m_themePreference == ThemePreference::Dark || (m_themePreference == ThemePreference::System && isSystemInDarkMode()); + darkModeSwitch(dark); + + repolishAfterThemeChanged(); +} + +void MainWindow::darkModeSwitch(bool darkMode) { m_isInDarkMode = darkMode; if (darkMode) { m_cBack.setColor(QPalette::Base, QColor(Qt::blue).lighter(180)); @@ -1162,13 +1242,27 @@ void MainWindow::changeEvent(QEvent* event) { } QMainWindow::changeEvent(event); if (eventType == QEvent::ThemeChange) { - bool darkMode = isSystemInDarkMode(); - if (darkMode != m_isInDarkMode) { - darkModeSwitch(darkMode); - } + applyThemeFromPreference(); } } +void MainWindow::setThemePreference(int index) { + const auto pref = static_cast(index); + + if (ui && ui->comboTheme && ui->comboTheme->currentIndex() != index) { + QSignalBlocker blocker(ui->comboTheme); + ui->comboTheme->setCurrentIndex(index); + } + + m_themePreference = pref; + + if (m_config && opts.useSettings) { + m_config->setValue(SETTING_UI_THEME, index); + } + + applyThemeFromPreference(); +} + void MainWindow::showEvent(QShowEvent *e) { if (!m_setup) { e->ignore(); @@ -1486,6 +1580,7 @@ void MainWindow::resetGui() { m_config->remove(SETTING_WINDOW_STATUSBAR); m_config->remove(SETTING_WINDOW_SEPARATOR); m_config->remove(SETTING_UI_EDIT_MODE); + m_config->remove(SETTING_UI_THEME); m_needReload = true; close(); } diff --git a/gui/qt/mainwindow.h b/gui/qt/mainwindow.h index 81623a71..89476a2e 100644 --- a/gui/qt/mainwindow.h +++ b/gui/qt/mainwindow.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,9 @@ QT_BEGIN_NAMESPACE class QButtonGroup; class QLocale; +class QEvent; +class QCloseEvent; +class QObject; QT_END_NAMESPACE #ifdef LIBUSB_SUPPORT @@ -99,6 +103,7 @@ private slots: #ifdef LIBUSB_SUPPORT void usbConnectPhysical(QVariant userData); #endif + void setThemePreference(int index); protected: virtual void changeEvent(QEvent* event) override; @@ -227,6 +232,12 @@ private slots: FULLSCREEN_LCD }; + enum class ThemePreference { + System = 0, + Light = 1, + Dark = 2 + }; + // emu keypresses void sendEmuKey(uint16_t key); void sendEmuLetterKey(char letter); @@ -257,6 +268,7 @@ private slots: void translateExtras(int init); void translateSwitch(const QLocale &locale); + void applyThemeFromPreference(); // dark mode void darkModeSwitch(bool darkMode); @@ -763,6 +775,8 @@ private slots: int m_fullscreen = FULLSCREEN_NONE; uint32_t m_runUntilAddr; + ThemePreference m_themePreference = ThemePreference::System; + QPushButton *m_btnCancelTranser; QProgressBar *m_progressBar; QStringList m_docksMemory; @@ -785,6 +799,8 @@ private slots: bool m_timerFpsTriggered = false; QString m_styleForMode[2]; + QPalette m_fusionLightPalette; + QPalette m_fusionDarkPalette; static const char *m_varExtensions[]; @@ -850,6 +866,7 @@ private slots: static const QString SETTING_STATUS_INTERVAL; static const QString SETTING_FIRST_RUN; static const QString SETTING_UI_EDIT_MODE; + static const QString SETTING_UI_THEME; static const QString SETTING_PAUSE_FOCUS; static const QString SETTING_SAVE_ON_CLOSE; static const QString SETTING_RESTORE_ON_OPEN; diff --git a/gui/qt/mainwindow.ui b/gui/qt/mainwindow.ui index 9f8b5b9f..96a2abc9 100644 --- a/gui/qt/mainwindow.ui +++ b/gui/qt/mainwindow.ui @@ -7771,6 +7771,45 @@ + + + + Theme: + + + + + + + + System Default + + + + + Light + + + + + Dark + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + diff --git a/gui/qt/settings.cpp b/gui/qt/settings.cpp index 9ecb06da..c915a5e6 100644 --- a/gui/qt/settings.cpp +++ b/gui/qt/settings.cpp @@ -94,6 +94,7 @@ const QString MainWindow::SETTING_ROM_PATH = QStringLiteral("ro const QString MainWindow::SETTING_STATUS_INTERVAL = QStringLiteral("status_interval"); const QString MainWindow::SETTING_FIRST_RUN = QStringLiteral("first_run"); const QString MainWindow::SETTING_UI_EDIT_MODE = QStringLiteral("ui_edit_mode"); +const QString MainWindow::SETTING_UI_THEME = QStringLiteral("ui_theme"); const QString MainWindow::SETTING_PAUSE_FOCUS = QStringLiteral("pause_on_focus_change"); const QString MainWindow::SETTING_SAVE_ON_CLOSE = QStringLiteral("save_on_close"); const QString MainWindow::SETTING_RESTORE_ON_OPEN = QStringLiteral("restore_on_open"); @@ -1277,6 +1278,7 @@ void MainWindow::saveSettings() { m_config->setValue(SETTING_WINDOW_VISUALIZER_CONFIG, m_docksVisualizerConfig); m_config->setValue(SETTING_WINDOW_KEYHISTORY_DOCKS, m_docksKeyHistory); m_config->setValue(SETTING_WINDOW_KEYHISTORY_CONFIG, QVariant::fromValue(m_docksKeyHistorySize)); + m_config->setValue(SETTING_UI_THEME, static_cast(m_themePreference)); // Disassembly Goto history { From 1d7f8f3e0fdd63bace1bc580a7df7ef693c686fe Mon Sep 17 00:00:00 2001 From: Sightem Date: Fri, 17 Oct 2025 10:17:48 -0700 Subject: [PATCH 2/3] switch to using setColorScheme --- gui/qt/mainwindow.cpp | 92 ++++++++++++++++++------------------------- gui/qt/mainwindow.h | 4 +- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index 6ded25d2..c873efbc 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,32 +47,6 @@ #include #include #include - -namespace { -QPalette createFusionDarkPalette(const QPalette &base) { - QPalette palette = base; - palette.setColor(QPalette::Window, QColor(53, 53, 53)); - palette.setColor(QPalette::WindowText, Qt::white); - palette.setColor(QPalette::Base, QColor(25, 25, 25)); - palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); - palette.setColor(QPalette::ToolTipBase, Qt::white); - palette.setColor(QPalette::ToolTipText, Qt::white); - palette.setColor(QPalette::Text, Qt::white); - palette.setColor(QPalette::Button, QColor(53, 53, 53)); - palette.setColor(QPalette::ButtonText, Qt::white); - palette.setColor(QPalette::BrightText, Qt::red); - palette.setColor(QPalette::Link, QColor(42, 130, 218)); - palette.setColor(QPalette::Highlight, QColor(76, 163, 224)); - palette.setColor(QPalette::HighlightedText, Qt::black); - - static constexpr QColor disabledColor(127, 127, 127); - palette.setColor(QPalette::Disabled, QPalette::Text, disabledColor); - palette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor); - palette.setColor(QPalette::PlaceholderText, disabledColor); - return palette; -} -} - #ifdef Q_OS_MACOS # include "os/mac/kdmactouchbar.h" #endif @@ -87,6 +62,8 @@ Q_DECLARE_METATYPE(emu_data_t) # include #endif +using namespace Qt::StringLiterals; + MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new Ui::MainWindow), opts(cliOpts) { keypadBridge = new QtKeypadBridge(this); // This must be before setupUi for some reason >.> @@ -101,23 +78,6 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U ui->setupUi(this); - m_styleForMode[0] = QApplication::style()->objectName(); - if (m_styleForMode[0].isEmpty()) { - m_styleForMode[0] = QApplication::style()->name(); - } - if (m_styleForMode[0].isEmpty()) { - m_styleForMode[0] = QStringLiteral("fusion"); - } - m_styleForMode[1] = QStringLiteral("fusion"); - - if (const QStyle *fusionStyle = QStyleFactory::create(QStringLiteral("Fusion"))) { - m_fusionLightPalette = fusionStyle->standardPalette(); - delete fusionStyle; - } else { - m_fusionLightPalette = QApplication::style()->standardPalette(); - } - m_fusionDarkPalette = createFusionDarkPalette(m_fusionLightPalette); - { QSignalBlocker blocker(ui->comboTheme); ui->comboTheme->setCurrentIndex(static_cast(m_themePreference)); @@ -1195,24 +1155,46 @@ static void repolishAfterThemeChanged() { } void MainWindow::applyThemeFromPreference() { + Qt::ColorScheme scheme = Qt::ColorScheme::Unknown; + bool explicitScheme = false; switch (m_themePreference) { case ThemePreference::System: - QApplication::setStyle(m_styleForMode[0]); - QApplication::setPalette(QApplication::style()->standardPalette()); + scheme = Qt::ColorScheme::Unknown; break; case ThemePreference::Light: - QApplication::setStyle(m_styleForMode[1]); - QApplication::setPalette(m_fusionLightPalette); + scheme = Qt::ColorScheme::Light; + explicitScheme = true; break; case ThemePreference::Dark: - QApplication::setStyle(m_styleForMode[1]); - QApplication::setPalette(m_fusionDarkPalette); + scheme = Qt::ColorScheme::Dark; + explicitScheme = true; break; } + qApp->styleHints()->setColorScheme(scheme); - const bool dark = m_themePreference == ThemePreference::Dark || (m_themePreference == ThemePreference::System && isSystemInDarkMode()); - darkModeSwitch(dark); +#if defined(Q_OS_WIN) + if (explicitScheme) { + if (QStyle *fusion = QStyleFactory::create("Fusion"_L1)) { + QApplication::setStyle(fusion); + } + } else { + const auto available = QStyleFactory::keys(); + if (available.contains("WindowsVista"_L1, Qt::CaseInsensitive)) { + if (QStyle *vista = QStyleFactory::create("WindowsVista"_L1)) { + QApplication::setStyle(vista); + } + } + } +#else + if (explicitScheme) { + if (QStyle *fusion = QStyleFactory::create("Fusion"_L1)) { + QApplication::setStyle(fusion); + } + } +#endif + const bool dark = (qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark); + darkModeSwitch(dark); repolishAfterThemeChanged(); } @@ -1240,8 +1222,12 @@ void MainWindow::changeEvent(QEvent* event) { translateSwitch(QLocale::system()); } QMainWindow::changeEvent(event); - if (eventType == QEvent::ThemeChange) { - applyThemeFromPreference(); + if (eventType == QEvent::ThemeChange || + eventType == QEvent::ApplicationPaletteChange || + eventType == QEvent::PaletteChange) { + const bool dark = (qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark); + darkModeSwitch(dark); + repolishAfterThemeChanged(); } } diff --git a/gui/qt/mainwindow.h b/gui/qt/mainwindow.h index 55ff1eb0..cc11cd03 100644 --- a/gui/qt/mainwindow.h +++ b/gui/qt/mainwindow.h @@ -804,9 +804,7 @@ private slots: bool m_timerEmuTriggered = false; bool m_timerFpsTriggered = false; - QString m_styleForMode[2]; - QPalette m_fusionLightPalette; - QPalette m_fusionDarkPalette; + static const char *m_varExtensions[]; From 17013fd695e442716a2a78b9e4ce4275ac76bb26 Mon Sep 17 00:00:00 2001 From: Sightem Date: Sun, 19 Oct 2025 10:34:21 -0700 Subject: [PATCH 3/3] address changing to fusion on mac, move the dropdown --- gui/qt/mainwindow.cpp | 15 ++------------- gui/qt/mainwindow.ui | 34 ++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index c873efbc..4a01ee35 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -1143,17 +1143,6 @@ void MainWindow::translateExtras(int init) { } } -static void repolishAfterThemeChanged() { - const auto widgets = QApplication::allWidgets(); - for (QWidget *w : widgets) { - if (QStyle *st = w->style()) { - st->unpolish(w); - st->polish(w); - } - w->update(); - } -} - void MainWindow::applyThemeFromPreference() { Qt::ColorScheme scheme = Qt::ColorScheme::Unknown; bool explicitScheme = false; @@ -1185,6 +1174,8 @@ void MainWindow::applyThemeFromPreference() { } } } +#elif defined(Q_OS_MACOS) + Q_UNUSED(explicitScheme); #else if (explicitScheme) { if (QStyle *fusion = QStyleFactory::create("Fusion"_L1)) { @@ -1195,7 +1186,6 @@ void MainWindow::applyThemeFromPreference() { const bool dark = (qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark); darkModeSwitch(dark); - repolishAfterThemeChanged(); } void MainWindow::darkModeSwitch(bool darkMode) { @@ -1227,7 +1217,6 @@ void MainWindow::changeEvent(QEvent* event) { eventType == QEvent::PaletteChange) { const bool dark = (qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark); darkModeSwitch(dark); - repolishAfterThemeChanged(); } } diff --git a/gui/qt/mainwindow.ui b/gui/qt/mainwindow.ui index 96a2abc9..4595f4a9 100644 --- a/gui/qt/mainwindow.ui +++ b/gui/qt/mainwindow.ui @@ -7682,7 +7682,7 @@ - + Qt::Horizontal @@ -7771,15 +7771,30 @@ - + Theme: - + + + + 0 + 0 + + + + QComboBox::AdjustToContents + + + + 200 + 16777215 + + System Default @@ -7797,19 +7812,6 @@ - - - - Qt::Horizontal - - - - 0 - 20 - - - -