Qt: Set QStyleHints colorScheme property properly
Some checks failed
🐧 Linux Builds / AppImage (push) Has been cancelled
🐧 Linux Builds / Flatpak (push) Has been cancelled
🍎 MacOS Builds / Defaults (push) Has been cancelled
🖥️ Windows Builds / Lint VS Project Files (push) Has been cancelled
🖥️ Windows Builds / CMake (push) Has been cancelled
🖥️ Windows Builds / SSE4 (push) Has been cancelled
🖥️ Windows Builds / AVX2 (push) Has been cancelled

This commit is contained in:
chaoticgd 2025-10-12 21:23:54 +01:00 committed by Ty
parent fe95a697f4
commit aedc51e151
8 changed files with 57 additions and 85 deletions

View File

@ -15,10 +15,6 @@ namespace CocoaTools
void DestroyMetalLayer(WindowInfo* wi); void DestroyMetalLayer(WindowInfo* wi);
std::optional<float> GetViewRefreshRate(const WindowInfo& wi); std::optional<float> 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 /// Mark an NSMenu as the help menu
void MarkHelpMenu(void* menu); void MarkHelpMenu(void* menu);
/// Returns the bundle path. /// Returns the bundle path.
@ -44,6 +40,6 @@ namespace CocoaTools
void RunCocoaEventLoop(bool wait_forever = false); void RunCocoaEventLoop(bool wait_forever = false);
/// Posts an event to the main telling `RunCocoaEventLoop(true)` to exit /// Posts an event to the main telling `RunCocoaEventLoop(true)` to exit
void StopMainThreadEventLoop(); void StopMainThreadEventLoop();
} } // namespace CocoaTools
#endif // __APPLE__ #endif // __APPLE__

View File

@ -85,63 +85,7 @@ std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
return ret; return ret;
} }
// MARK: - Theme Change Handlers // MARK: - Help menu
@interface PCSX2KVOHelper : NSObject
- (void)addCallback:(void*)ctx run:(void(*)(void*))callback;
- (void)removeCallback:(void*)ctx;
@end
@implementation PCSX2KVOHelper
{
std::vector<std::pair<void*, void(*)(void*)>> _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<NSKeyValueChangeKey,id> *)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];
}
void CocoaTools::MarkHelpMenu(void* menu) void CocoaTools::MarkHelpMenu(void* menu)
{ {

View File

@ -939,7 +939,7 @@ inline QString DisassemblyView::DisassemblyStringFromAddress(u32 address, QFont
QColor DisassemblyView::GetAddressFunctionColor(u32 address) QColor DisassemblyView::GetAddressFunctionColor(u32 address)
{ {
std::array<QColor, 6> colors; std::array<QColor, 6> colors;
if (QtUtils::IsLightTheme(palette())) if (!QtHost::IsDarkApplicationTheme())
{ {
colors = { colors = {
QColor::fromRgba(0xFFFA3434), QColor::fromRgba(0xFFFA3434),

View File

@ -3,6 +3,7 @@
#include "DropIndicators.h" #include "DropIndicators.h"
#include "QtHost.h"
#include "QtUtils.h" #include "QtUtils.h"
#include "Debugger/Docking/DockViews.h" #include "Debugger/Docking/DockViews.h"
@ -21,7 +22,7 @@ static std::pair<QColor, QColor> pickNiceColours(const QPalette& palette, bool h
QColor fill = palette.highlight().color(); QColor fill = palette.highlight().color();
QColor outline = palette.highlight().color(); QColor outline = palette.highlight().color();
if (QtUtils::IsLightTheme(palette)) if (!QtHost::IsDarkApplicationTheme())
{ {
fill = fill.darker(200); fill = fill.darker(200);
outline = outline.darker(200); outline = outline.darker(200);

View File

@ -138,21 +138,11 @@ MainWindow::~MainWindow()
#ifdef _WIN32 #ifdef _WIN32
unregisterForDeviceNotifications(); unregisterForDeviceNotifications();
#endif #endif
#ifdef __APPLE__
CocoaTools::RemoveThemeChangeHandler(this);
#endif
} }
void MainWindow::initialize() void MainWindow::initialize()
{ {
#ifdef __APPLE__ #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<MainWindow*>(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 // The cocoa backing isn't initialized yet, delay this until stuff is set up with a `RunOnUIThread` call
QtHost::RunOnUIThread([this] { QtHost::RunOnUIThread([this] {
CocoaTools::MarkHelpMenu(m_ui.menuHelp->toNSMenu()); CocoaTools::MarkHelpMenu(m_ui.menuHelp->toNSMenu());

View File

@ -90,7 +90,9 @@ namespace QtUtils
const int min_column_width = header->minimumSectionSize(); const int min_column_width = header->minimumSectionSize();
const int scrollbar_width = ((view->verticalScrollBar() && view->verticalScrollBar()->isVisible()) || 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 num_flex_items = 0;
int total_width = 0; int total_width = 0;
int column_index = 0; int column_index = 0;
@ -346,11 +348,6 @@ namespace QtUtils
return csv; return csv;
} }
bool IsLightTheme(const QPalette& palette)
{
return palette.text().color().lightnessF() < 0.5;
}
bool IsCompositorManagerRunning() bool IsCompositorManagerRunning()
{ {
if (qEnvironmentVariableIsSet("PCSX2_NO_COMPOSITING")) if (qEnvironmentVariableIsSet("PCSX2_NO_COMPOSITING"))

View File

@ -96,9 +96,7 @@ namespace QtUtils
/// Converts an abstract item model to a CSV string. /// Converts an abstract item model to a CSV string.
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole, bool useQuotes = false); QString AbstractItemModelToCSV(QAbstractItemModel* model, int role = Qt::DisplayRole, bool useQuotes = false);
// Heuristic to check if the current theme is a light or dark theme. /// Checks if we can use transparency effects e.g. for dock drop indicators.
bool IsLightTheme(const QPalette& palette);
bool IsCompositorManagerRunning(); bool IsCompositorManagerRunning();
/// Sets the scalable icon to a given label (svg icons, or icons with multiple size pixmaps) /// Sets the scalable icon to a given label (svg icons, or icons with multiple size pixmaps)

View File

@ -11,6 +11,7 @@
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtGui/QPalette> #include <QtGui/QPalette>
#include <QtGui/QPixmapCache> #include <QtGui/QPixmapCache>
#include <QtGui/QStyleHints>
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include <QtWidgets/QStyle> #include <QtWidgets/QStyle>
#include <QtWidgets/QStyleFactory> #include <QtWidgets/QStyleFactory>
@ -18,12 +19,19 @@
namespace QtHost namespace QtHost
{ {
static void SetStyleFromSettings(); static void SetStyleFromSettings();
static void SetColorScheme(Qt::ColorScheme color_scheme);
} // namespace QtHost } // namespace QtHost
static QString s_unthemed_style_name; static QString s_unthemed_style_name;
static QPalette s_unthemed_palette; static QPalette s_unthemed_palette;
static bool s_unthemed_style_name_set; 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() const char* QtHost::GetDefaultThemeName()
{ {
#ifdef __APPLE__ #ifdef __APPLE__
@ -54,8 +62,18 @@ void QtHost::UpdateApplicationTheme()
bool QtHost::IsDarkApplicationTheme() 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(); 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() void QtHost::SetIconThemeFromStyle()
@ -73,6 +91,7 @@ void QtHost::SetStyleFromSettings()
qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setStyle(QStyleFactory::create("Fusion"));
qApp->setPalette(s_unthemed_palette); qApp->setPalette(s_unthemed_palette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Unknown);
} }
#ifdef _WIN32 #ifdef _WIN32
else if (theme == "windowsvista") else if (theme == "windowsvista")
@ -80,6 +99,10 @@ void QtHost::SetStyleFromSettings()
qApp->setStyle(QStyleFactory::create("windowsvista")); qApp->setStyle(QStyleFactory::create("windowsvista"));
qApp->setPalette(s_unthemed_palette); qApp->setPalette(s_unthemed_palette);
qApp->setStyleSheet(QString()); 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 #endif
else if (theme == "darkfusion") else if (theme == "darkfusion")
@ -116,6 +139,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(darkPalette); qApp->setPalette(darkPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "darkfusionblue") else if (theme == "darkfusionblue")
{ {
@ -151,6 +175,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(darkBluePalette); qApp->setPalette(darkBluePalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "GreyMatter") else if (theme == "GreyMatter")
{ {
@ -187,6 +212,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(greyMatterPalette); qApp->setPalette(greyMatterPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "UntouchedLagoon") else if (theme == "UntouchedLagoon")
{ {
@ -222,6 +248,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(untouchedLagoonPalette); qApp->setPalette(untouchedLagoonPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Light);
} }
else if (theme == "BabyPastel") else if (theme == "BabyPastel")
{ {
@ -259,6 +286,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(babyPastelPalette); qApp->setPalette(babyPastelPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Light);
} }
else if (theme == "PizzaBrown") else if (theme == "PizzaBrown")
{ {
@ -296,6 +324,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(pizzaPalette); qApp->setPalette(pizzaPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Light);
} }
else if (theme == "PCSX2Blue") else if (theme == "PCSX2Blue")
{ {
@ -331,6 +360,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(pcsx2BluePalette); qApp->setPalette(pcsx2BluePalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Light);
} }
else if (theme == "ScarletDevilRed") else if (theme == "ScarletDevilRed")
{ {
@ -364,6 +394,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(scarletDevilPalette); qApp->setPalette(scarletDevilPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "VioletAngelPurple") else if (theme == "VioletAngelPurple")
{ {
@ -397,6 +428,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(violetAngelPalette); qApp->setPalette(violetAngelPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "CobaltSky") else if (theme == "CobaltSky")
{ {
@ -434,6 +466,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(cobaltSkyPalette); qApp->setPalette(cobaltSkyPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "AMOLED") else if (theme == "AMOLED")
{ {
@ -470,6 +503,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(AMOLEDPalette); qApp->setPalette(AMOLEDPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "Ruby") else if (theme == "Ruby")
{ {
@ -503,6 +537,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(rubyPalette); qApp->setPalette(rubyPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "Sapphire") else if (theme == "Sapphire")
{ {
@ -536,6 +571,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(sapphirePalette); qApp->setPalette(sapphirePalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "Emerald") else if (theme == "Emerald")
{ {
@ -569,6 +605,7 @@ void QtHost::SetStyleFromSettings()
qApp->setPalette(emeraldPalette); qApp->setPalette(emeraldPalette);
qApp->setStyleSheet(QString()); qApp->setStyleSheet(QString());
SetColorScheme(Qt::ColorScheme::Dark);
} }
else if (theme == "Custom") else if (theme == "Custom")
{ {
@ -587,11 +624,20 @@ void QtHost::SetStyleFromSettings()
{ {
qApp->setStyle(QStyleFactory::create("Fusion")); qApp->setStyle(QStyleFactory::create("Fusion"));
} }
SetColorScheme(Qt::ColorScheme::Unknown);
} }
else else
{ {
qApp->setStyle(s_unthemed_style_name); qApp->setStyle(s_unthemed_style_name);
qApp->setPalette(s_unthemed_palette); qApp->setPalette(s_unthemed_palette);
qApp->setStyleSheet(QString()); 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);
}