From f8d5379f8e7106b35e99a141a5078fd3f0891c98 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 5 Feb 2023 15:00:51 +1000 Subject: [PATCH] Qt: Default to native/dynamic theme on Mac --- src/duckstation-qt/mainwindow.cpp | 32 +++++++++---- src/duckstation-qt/mainwindow.h | 1 + src/frontend-common/platform_misc.h | 7 +++ src/frontend-common/platform_misc_mac.mm | 61 ++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index ea87ca8ac..d5b5c53f2 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -18,6 +18,7 @@ #include "debuggerwindow.h" #include "displaywidget.h" #include "frontend-common/game_list.h" +#include "frontend-common/platform_misc.h" #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "gdbserver.h" @@ -62,7 +63,11 @@ static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP( "(*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe " "*.psexe *.ps-exe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u)"); -static const char* DEFAULT_THEME_NAME = "darkfusion"; +#ifdef __APPLE__ +const char* DEFAULT_THEME_NAME = ""; +#else +const char* DEFAULT_THEME_NAME = "darkfusion"; +#endif MainWindow* g_main_window = nullptr; static QString s_unthemed_style_name; @@ -116,6 +121,9 @@ MainWindow::~MainWindow() #ifdef _WIN32 unregisterForDeviceNotifications(); #endif +#ifdef __APPLE__ + FrontendCommon::RemoveThemeChangeHandler(this); +#endif } void MainWindow::updateApplicationTheme() @@ -149,6 +157,11 @@ void MainWindow::initialize() #ifdef _WIN32 registerForDeviceNotifications(); #endif + +#ifdef __APPLE__ + FrontendCommon::AddThemeChangeHandler(this, + [](void* ctx) { QtHost::RunOnUIThread([] { g_main_window->updateTheme(); }); }); +#endif } void MainWindow::reportError(const QString& title, const QString& message) @@ -2078,6 +2091,11 @@ void MainWindow::setTheme(const QString& theme) { Host::SetBaseStringSettingValue("UI", "Theme", theme.toUtf8().constData()); Host::CommitBaseSettingChanges(); + updateTheme(); +} + +void MainWindow::updateTheme() +{ updateApplicationTheme(); updateMenuSelectedTheme(); m_game_list_widget->reloadCommonImages(); @@ -2185,15 +2203,9 @@ void MainWindow::setStyleFromSettings() void MainWindow::setIconThemeFromSettings() { - const std::string theme(Host::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME)); - QString icon_theme; - - if (theme == "qdarkstyle" || theme == "darkfusion" || theme == "darkfusionblue") - icon_theme = QStringLiteral("white"); - else - icon_theme = QStringLiteral("black"); - - QIcon::setThemeName(icon_theme); + const QPalette palette(qApp->palette()); + const bool dark = palette.windowText().color().value() > palette.window().color().value(); + QIcon::setThemeName(dark ? QStringLiteral("white") : QStringLiteral("black")); } void MainWindow::onSettingsResetToDefault() diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index c0adae732..ec1aa123f 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -228,6 +228,7 @@ private: void setGameListEntryCoverImage(const GameList::Entry* entry); void clearGameListEntryPlayTime(const GameList::Entry* entry); void setTheme(const QString& theme); + void updateTheme(); void recreate(); void registerForDeviceNotifications(); diff --git a/src/frontend-common/platform_misc.h b/src/frontend-common/platform_misc.h index a1569a3bb..8d9d496dc 100644 --- a/src/frontend-common/platform_misc.h +++ b/src/frontend-common/platform_misc.h @@ -10,4 +10,11 @@ void ResumeScreensaver(); /// Abstracts platform-specific code for asynchronously playing a sound. /// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound. bool PlaySoundAsync(const char* path); + +#ifdef __APPLE__ +/// Add a handler to be run when macOS changes between dark and light themes +void AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)); +/// Remove a handler previously added using AddThemeChangeHandler with the given context +void RemoveThemeChangeHandler(void* ctx); +#endif } // namespace FrontendCommon diff --git a/src/frontend-common/platform_misc_mac.mm b/src/frontend-common/platform_misc_mac.mm index 5ccc13083..efe510a03 100644 --- a/src/frontend-common/platform_misc_mac.mm +++ b/src/frontend-common/platform_misc_mac.mm @@ -5,7 +5,10 @@ #include "common/log.h" #include "common/string.h" #include +#include +#include #include +#include Log_SetChannel(FrontendCommon); #import @@ -69,3 +72,61 @@ bool FrontendCommon::PlaySoundAsync(const char* path) [nspath release]; return result; } + +// From https://github.com/PCSX2/pcsx2/blob/1b673d9dd0829a48f5f0b6604c1de2108e981399/common/CocoaTools.mm + +@interface PCSX2KVOHelper : NSObject + +- (void)addCallback:(void*)ctx run:(void(*)(void*))callback; +- (void)removeCallback:(void*)ctx; + +@end + +@implementation PCSX2KVOHelper +{ + std::vector> _callbacks; +} + +- (void)addCallback:(void*)ctx run:(void(*)(void*))callback +{ + _callbacks.push_back(std::make_pair(ctx, callback)); +} + +- (void)removeCallback:(void*)ctx +{ + auto new_end = std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry){ + return ctx == entry.first; + }); + _callbacks.erase(new_end, _callbacks.end()); +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + for (const auto& callback : _callbacks) + callback.second(callback.first); +} + +@end + +static PCSX2KVOHelper* s_themeChangeHandler; + +void FrontendCommon::AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)) +{ + assert([NSThread isMainThread]); + if (!s_themeChangeHandler) + { + s_themeChangeHandler = [[PCSX2KVOHelper alloc] init]; + NSApplication* app = [NSApplication sharedApplication]; + [app addObserver:s_themeChangeHandler + forKeyPath:@"effectiveAppearance" + options:0 + context:nil]; + } + [s_themeChangeHandler addCallback:ctx run:handler]; +} + +void FrontendCommon::RemoveThemeChangeHandler(void* ctx) +{ + assert([NSThread isMainThread]); + [s_themeChangeHandler removeCallback:ctx]; +}