From a654ce63bf00c7a5115858769c0441780490a497 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 12 Oct 2025 00:50:32 +1000 Subject: [PATCH] Qt: Style QMenu and QToolButton with stylesheet on MacOS Using a QStyleProxy would be preferable, but this is fast enough and stops the native theme from looking so ugly. --- src/duckstation-qt/mainwindow.cpp | 2 +- src/duckstation-qt/qthost.h | 2 +- src/duckstation-qt/qtthemes.cpp | 125 ++++++++++++++++++++++++++++-- 3 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 06d1ee266..89ac4d885 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2891,7 +2891,7 @@ void MainWindow::changeEvent(QEvent* event) if (event->type() == QEvent::StyleChange) { - QtHost::SetIconThemeFromStyle(); + QtHost::UpdateThemeOnStyleChange(); emit themeChanged(QtHost::IsDarkApplicationTheme()); } diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 16365bc71..9a5dd114f 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -352,7 +352,7 @@ bool IsDarkApplicationTheme(); bool IsStyleSheetApplicationTheme(); /// Sets the icon theme, based on the current style (light/dark). -void SetIconThemeFromStyle(); +void UpdateThemeOnStyleChange(); /// Sets batch mode (exit after game shutdown). bool InBatchMode(); diff --git a/src/duckstation-qt/qtthemes.cpp b/src/duckstation-qt/qtthemes.cpp index 8d05e12b4..81d284fda 100644 --- a/src/duckstation-qt/qtthemes.cpp +++ b/src/duckstation-qt/qtthemes.cpp @@ -17,11 +17,12 @@ namespace QtHost { static void SetThemeAttributes(bool is_stylesheet_theme, bool is_variable_color_theme, bool is_dark_theme); static void SetStyleFromSettings(); +static QString GetNativeThemeStylesheet(); +static bool NativeThemeStylesheetNeedsUpdate(); namespace { struct State { - std::string current_theme_name; QString unthemed_style_name; QPalette unthemed_palette; bool is_stylesheet_theme = false; @@ -50,7 +51,7 @@ void QtHost::UpdateApplicationTheme() } SetStyleFromSettings(); - SetIconThemeFromStyle(); + UpdateThemeOnStyleChange(); } void QtHost::SetThemeAttributes(bool is_stylesheet_theme, bool is_variable_color_theme, bool is_dark_theme) @@ -502,7 +503,7 @@ QToolBar { SetThemeAttributes(false, true, false); qApp->setStyle(s_state.unthemed_style_name); qApp->setPalette(s_state.unthemed_palette); - qApp->setStyleSheet(QString()); + qApp->setStyleSheet(GetNativeThemeStylesheet()); } } @@ -524,9 +525,14 @@ bool QtHost::IsStyleSheetApplicationTheme() return s_state.is_stylesheet_theme; } -void QtHost::SetIconThemeFromStyle() +void QtHost::UpdateThemeOnStyleChange() { - QIcon::setThemeName(IsDarkApplicationTheme() ? QStringLiteral("white") : QStringLiteral("black")); + const QString new_theme_name = IsDarkApplicationTheme() ? QStringLiteral("white") : QStringLiteral("black"); + if (QIcon::themeName() != new_theme_name) + QIcon::setThemeName(new_theme_name); + + if (NativeThemeStylesheetNeedsUpdate()) + qApp->setStyleSheet(GetNativeThemeStylesheet()); } const char* Host::GetDefaultFullscreenUITheme() @@ -552,3 +558,112 @@ const char* Host::GetDefaultFullscreenUITheme() else return IsDarkApplicationTheme() ? "Dark" : "Light"; } + +bool QtHost::NativeThemeStylesheetNeedsUpdate() +{ +#ifdef __APPLE__ + // See below, only used on MacOS. + return s_state.is_variable_color_theme; +#else + return false; +#endif +} + +QString QtHost::GetNativeThemeStylesheet() +{ + QString ret; +#ifdef __APPLE__ + // Qt's native style on MacOS is... not great. + // We re-theme the tool buttons to look like Cocoa tool buttons, and fix up popup menus. + ret = QStringLiteral(R"( +QMenu { + border-radius: 10px; + padding: 4px 0; +} +QMenu::item { + padding: 4px 15px; + border-radius: 8px; + margin: 0 2px; +} +QMenu::icon, +QMenu::indicator { + left: 8px; +} +QMenu::icon:checked { + border-radius: 4px; +} +QMenu::separator { + height: 1px; + margin: 4px 8px; +} +QToolButton { + border: none; + background: transparent; + padding: 5px; + border-radius: 10px; +})"); + if (IsDarkApplicationTheme()) + { + ret += QStringLiteral(R"( +QMenu { + background-color: #161616; + border: 1px solid #2c2c2c; +} +QMenu::item { + color: #dcdcdc; +} +QMenu::item:selected { + background-color: #2b4ab3; + color: #ffffff; +} +QMenu::icon:checked { + background: #414141; + border: 1px solid #777; +} +QMenu::separator { + background: #3b3b3b; +} +QToolButton:checked { + background-color: #454645; +} +QToolButton:hover { + background-color: #393c3c; +} +QToolButton:pressed { + background-color: #808180; +})"); + } + else + { + ret += QStringLiteral(R"( +QMenu { + background-color: #bdbdbd; + border: 1px solid #d5d5d4; +} +QMenu::item { + color: #1d1d1d; +} +QMenu::item:selected { + background-color: #2e5dc9; + color: #ffffff; +} +QMenu::icon:checked { + background: #414141; + border: 1px solid #777; +} +QMenu::separator { + background: #a9a9a9; +} +QToolButton:checked { + background-color: #e2e2e2; +} +QToolButton:hover { + background-color: #f0f0f0; +} +QToolButton:pressed { + background-color: #8c8c8c; +})"); + } +#endif + return ret; +}