diff --git a/common/CocoaTools.h b/common/CocoaTools.h index 22e3cf0bc5..71db89058c 100644 --- a/common/CocoaTools.h +++ b/common/CocoaTools.h @@ -15,10 +15,6 @@ namespace CocoaTools void DestroyMetalLayer(WindowInfo* wi); std::optional GetViewRefreshRate(const WindowInfo& wi); - /// 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); /// Mark an NSMenu as the help menu void MarkHelpMenu(void* menu); /// Returns the bundle path. @@ -44,6 +40,6 @@ namespace CocoaTools void RunCocoaEventLoop(bool wait_forever = false); /// Posts an event to the main telling `RunCocoaEventLoop(true)` to exit void StopMainThreadEventLoop(); -} +} // namespace CocoaTools #endif // __APPLE__ diff --git a/common/CocoaTools.mm b/common/CocoaTools.mm index 44b9659c3d..db9cd97e89 100644 --- a/common/CocoaTools.mm +++ b/common/CocoaTools.mm @@ -85,63 +85,7 @@ std::optional CocoaTools::GetViewRefreshRate(const WindowInfo& wi) return ret; } -// MARK: - Theme Change Handlers - -@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 CocoaTools::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 CocoaTools::RemoveThemeChangeHandler(void* ctx) -{ - assert([NSThread isMainThread]); - [s_themeChangeHandler removeCallback:ctx]; -} +// MARK: - Help menu void CocoaTools::MarkHelpMenu(void* menu) { diff --git a/pcsx2-qt/Debugger/DisassemblyView.cpp b/pcsx2-qt/Debugger/DisassemblyView.cpp index 74cb14b291..2deeeff7f5 100644 --- a/pcsx2-qt/Debugger/DisassemblyView.cpp +++ b/pcsx2-qt/Debugger/DisassemblyView.cpp @@ -939,7 +939,7 @@ inline QString DisassemblyView::DisassemblyStringFromAddress(u32 address, QFont QColor DisassemblyView::GetAddressFunctionColor(u32 address) { std::array colors; - if (QtUtils::IsLightTheme(palette())) + if (!QtHost::IsDarkApplicationTheme()) { colors = { QColor::fromRgba(0xFFFA3434), diff --git a/pcsx2-qt/Debugger/Docking/DropIndicators.cpp b/pcsx2-qt/Debugger/Docking/DropIndicators.cpp index 1a6ca586eb..ef160f9913 100644 --- a/pcsx2-qt/Debugger/Docking/DropIndicators.cpp +++ b/pcsx2-qt/Debugger/Docking/DropIndicators.cpp @@ -3,6 +3,7 @@ #include "DropIndicators.h" +#include "QtHost.h" #include "QtUtils.h" #include "Debugger/Docking/DockViews.h" @@ -21,7 +22,7 @@ static std::pair pickNiceColours(const QPalette& palette, bool h QColor fill = palette.highlight().color(); QColor outline = palette.highlight().color(); - if (QtUtils::IsLightTheme(palette)) + if (!QtHost::IsDarkApplicationTheme()) { fill = fill.darker(200); outline = outline.darker(200); @@ -197,7 +198,7 @@ static const constexpr int INDICATOR_MARGIN = 10; static bool isWayland() { return KDDockWidgets::Core::Platform::instance()->displayType() == - KDDockWidgets::Core::Platform::DisplayType::Wayland; + KDDockWidgets::Core::Platform::DisplayType::Wayland; } static QWidget* parentForIndicatorWindow(KDDockWidgets::Core::ClassicDropIndicatorOverlay* classic_indicators) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 89a9e24acb..0ef3b9f5f7 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -138,21 +138,11 @@ MainWindow::~MainWindow() #ifdef _WIN32 unregisterForDeviceNotifications(); #endif -#ifdef __APPLE__ - CocoaTools::RemoveThemeChangeHandler(this); -#endif } void MainWindow::initialize() { #ifdef __APPLE__ - CocoaTools::AddThemeChangeHandler(this, [](void* ctx) { - // This handler is called *before* the style change has propagated far enough for Qt to see it - // Use RunOnUIThread to delay until it has - QtHost::RunOnUIThread([ctx = static_cast(ctx)] { - ctx->updateTheme(); // Qt won't notice the style change without us touching the palette in some way - }); - }); // The cocoa backing isn't initialized yet, delay this until stuff is set up with a `RunOnUIThread` call QtHost::RunOnUIThread([this] { CocoaTools::MarkHelpMenu(m_ui.menuHelp->toNSMenu()); diff --git a/pcsx2-qt/QtUtils.cpp b/pcsx2-qt/QtUtils.cpp index d646dd02cc..93fe4dad10 100644 --- a/pcsx2-qt/QtUtils.cpp +++ b/pcsx2-qt/QtUtils.cpp @@ -90,7 +90,9 @@ namespace QtUtils const int min_column_width = header->minimumSectionSize(); const int scrollbar_width = ((view->verticalScrollBar() && view->verticalScrollBar()->isVisible()) || - view->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOn) ? view->verticalScrollBar()->width() : 0; + view->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOn) ? + view->verticalScrollBar()->width() : + 0; int num_flex_items = 0; int total_width = 0; int column_index = 0; @@ -346,11 +348,6 @@ namespace QtUtils return csv; } - bool IsLightTheme(const QPalette& palette) - { - return palette.text().color().lightnessF() < 0.5; - } - bool IsCompositorManagerRunning() { if (qEnvironmentVariableIsSet("PCSX2_NO_COMPOSITING")) diff --git a/pcsx2-qt/QtUtils.h b/pcsx2-qt/QtUtils.h index b70d3b533f..9978ec0be3 100644 --- a/pcsx2-qt/QtUtils.h +++ b/pcsx2-qt/QtUtils.h @@ -96,9 +96,7 @@ namespace QtUtils /// Converts an abstract item model to a CSV string. QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole, bool useQuotes = false); - // Heuristic to check if the current theme is a light or dark theme. - bool IsLightTheme(const QPalette& palette); - + /// Checks if we can use transparency effects e.g. for dock drop indicators. bool IsCompositorManagerRunning(); /// Sets the scalable icon to a given label (svg icons, or icons with multiple size pixmaps) diff --git a/pcsx2-qt/Themes.cpp b/pcsx2-qt/Themes.cpp index 0f85de7ac7..3b6d371288 100644 --- a/pcsx2-qt/Themes.cpp +++ b/pcsx2-qt/Themes.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,12 +19,19 @@ namespace QtHost { static void SetStyleFromSettings(); + static void SetColorScheme(Qt::ColorScheme color_scheme); } // namespace QtHost static QString s_unthemed_style_name; static QPalette s_unthemed_palette; static bool s_unthemed_style_name_set; +// This is different than the result of qApp->styleHints()->colorScheme() since +// if we set that to Qt::ColorScheme::Unknown it would return what the Qt +// platform code thinks is the correct color scheme instead (it can still return +// Qt::ColorScheme::Unknown if it doesn't know). +static Qt::ColorScheme s_color_scheme = Qt::ColorScheme::Unknown; + const char* QtHost::GetDefaultThemeName() { #ifdef __APPLE__ @@ -54,8 +62,18 @@ void QtHost::UpdateApplicationTheme() bool QtHost::IsDarkApplicationTheme() { + // If the current theme uses a fixed color scheme just return that. + if (s_color_scheme != Qt::ColorScheme::Unknown) + return s_color_scheme == Qt::ColorScheme::Dark; + + // Otherwise, fallback to using the palette heuristic. We don't bother + // asking the Qt platform code because it sometimes returns the wrong + // result. In particular, if the Windows Classic (windowsvista) theme is + // applied, and dark mode is enabled in the OS, it will return + // Qt::ColorScheme::Dark even though it's a light theme. We also can't treat + // it as a fixed color theme because of high contrast mode. QPalette palette = qApp->palette(); - return (palette.windowText().color().value() > palette.window().color().value()); + return palette.windowText().color().value() > palette.window().color().value(); } void QtHost::SetIconThemeFromStyle() @@ -73,6 +91,7 @@ void QtHost::SetStyleFromSettings() qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setPalette(s_unthemed_palette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Unknown); } #ifdef _WIN32 else if (theme == "windowsvista") @@ -80,6 +99,10 @@ void QtHost::SetStyleFromSettings() qApp->setStyle(QStyleFactory::create("windowsvista")); qApp->setPalette(s_unthemed_palette); qApp->setStyleSheet(QString()); + + // We can't set this to Qt::ColorScheme::Light because that breaks high + // contrast themes on Windows. + SetColorScheme(Qt::ColorScheme::Unknown); } #endif else if (theme == "darkfusion") @@ -116,6 +139,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(darkPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "darkfusionblue") { @@ -151,6 +175,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(darkBluePalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "GreyMatter") { @@ -187,6 +212,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(greyMatterPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "UntouchedLagoon") { @@ -222,6 +248,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(untouchedLagoonPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Light); } else if (theme == "BabyPastel") { @@ -259,6 +286,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(babyPastelPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Light); } else if (theme == "PizzaBrown") { @@ -296,6 +324,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(pizzaPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Light); } else if (theme == "PCSX2Blue") { @@ -331,6 +360,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(pcsx2BluePalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Light); } else if (theme == "ScarletDevilRed") { @@ -364,6 +394,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(scarletDevilPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "VioletAngelPurple") { @@ -397,6 +428,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(violetAngelPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "CobaltSky") { @@ -434,6 +466,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(cobaltSkyPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "AMOLED") { @@ -470,6 +503,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(AMOLEDPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "Ruby") { @@ -503,6 +537,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(rubyPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "Sapphire") { @@ -536,6 +571,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(sapphirePalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "Emerald") { @@ -569,6 +605,7 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(emeraldPalette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Dark); } else if (theme == "Custom") { @@ -587,11 +624,20 @@ void QtHost::SetStyleFromSettings() { qApp->setStyle(QStyleFactory::create("Fusion")); } + + SetColorScheme(Qt::ColorScheme::Unknown); } else { qApp->setStyle(s_unthemed_style_name); qApp->setPalette(s_unthemed_palette); qApp->setStyleSheet(QString()); + SetColorScheme(Qt::ColorScheme::Unknown); } } + +static void QtHost::SetColorScheme(Qt::ColorScheme color_scheme) +{ + s_color_scheme = color_scheme; + qApp->styleHints()->setColorScheme(color_scheme); +}