diff options
author | Tor Arne Vestbø <[email protected]> | 2025-04-15 23:46:13 +0200 |
---|---|---|
committer | Tor Arne Vestbø <[email protected]> | 2025-05-22 18:21:37 +0200 |
commit | ee78c173e9cb2a3ec6eac9b67b760bdd1904ff26 (patch) | |
tree | 5d38d59e5ac0067cd582aefe4b59f6f4a556893b | |
parent | 82015992c853b50dac167da26b8b858ac4794c66 (diff) |
macOS: Port from deprecated NSAppearance.currentAppearance
When the system appearance changes, it will be reflected through
NSApp.effectiveAppearance, but NSAppearance.currentAppearance
and NSAppearance.currentDrawingAppearance will remain as is for
some reason (perhaps as a compatibility for apps that were not
ready to handle dark mode).
The original way to deal with this was to explicitly set the
NSAppearance.currentAppearance to the new effective appearance,
which would take effect for the thread from that point on. But
this API has been deprecated, most likely because overriding it
globally could mess up views or logic that was not prepared for
dark mode).
The replacement API, NSAppearance performAsCurrentDrawingAppearance
is given a block, and will only override NSAppearance.currentAppearance
during the block, which isolates it from other fragile components.
We generally use NSApp.effectiveAppearance in our own code when
resolving dark mode, so we only need to wrap AppKit APIs we use
with performAsCurrentDrawingAppearance, including NSColor for our
palette, and NSView/NSControl drawing in our styles.
For the latter case we introduce a helper that we can also use
in the Qt Quick Mac style. This helper can also be the basis
of shared style functionality between Widgets and Quick going
forward.
Fixes: QTBUG-135789
Done-with: Volker Hilsheimer <[email protected]>
Change-Id: I4d006b8b1fbbd7ff888da8b903066c183adad188
Reviewed-by: Doris Verria <[email protected]>
-rw-r--r-- | src/gui/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/gui/platform/macos/qmacstyle_p.h | 81 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoatheme.mm | 19 | ||||
-rw-r--r-- | src/plugins/styles/mac/main.mm | 2 | ||||
-rw-r--r-- | src/plugins/styles/mac/qmacstyle_mac.mm | 44 | ||||
-rw-r--r-- | src/plugins/styles/mac/qmacstyle_mac_p.h | 5 |
6 files changed, 108 insertions, 44 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ba77cb1cccc..024bcfab426 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -395,6 +395,7 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_opengl qt_internal_extend_target(Gui CONDITION MACOS SOURCES platform/macos/qcocoanativeinterface.mm + platform/macos/qmacstyle_p.h LIBRARIES ${FWAppKit} ${FWCarbon} diff --git a/src/gui/platform/macos/qmacstyle_p.h b/src/gui/platform/macos/qmacstyle_p.h new file mode 100644 index 00000000000..bbfbc1a06e3 --- /dev/null +++ b/src/gui/platform/macos/qmacstyle_p.h @@ -0,0 +1,81 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMACSTYLE_P_H +#define QMACSTYLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qtguiglobal.h> + +#if defined(QT_WIDGETS_LIB) && defined(QT_QUICK_LIB) +# error "Cannot use QtGui Mac style with both Widgets and Quick" +#endif + +#if defined(QT_WIDGETS_LIB) +# define OPTIONAL_WIDGET_ARGUMENT , const QWidget *w = nullptr +# define FORWARD_OPTIONAL_WIDGET_ARGUMENT , w +# else +# define OPTIONAL_WIDGET_ARGUMENT +# define FORWARD_OPTIONAL_WIDGET_ARGUMENT +#endif + +#include <AppKit/NSApplication.h> + +QT_BEGIN_NAMESPACE + +/* + Helper class to ensure that the Mac style in Widgets and Quick + draw their NSViews and NSCells with the correct appearance, + as the native controls use NSAppearance.currentDrawingAppearance + instead of NSApp.effectiveAppearance when drawing. + + Due to the duplicated class hierarchies between Widgets and Quick + for the styles, with the Quick styles missing the QWidget pointer + in the function parameters, we have to opt for an awkward macro + to solve this. +*/ +template <typename Style> +class QMacApperanceStyle : public Style +{ +public: + void drawPrimitive(typename Style::PrimitiveElement pe, const QStyleOption *opt, QPainter *p + OPTIONAL_WIDGET_ARGUMENT) const override + { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + Style::drawPrimitive(pe, opt, p + FORWARD_OPTIONAL_WIDGET_ARGUMENT); + }]; + } + + void drawControl(typename Style::ControlElement element, const QStyleOption *opt, QPainter *p + OPTIONAL_WIDGET_ARGUMENT) const override + { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + Style::drawControl(element, opt, p + FORWARD_OPTIONAL_WIDGET_ARGUMENT); + }]; + } + + void drawComplexControl(typename Style::ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p + OPTIONAL_WIDGET_ARGUMENT) const override + { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + Style::drawComplexControl(cc, opt, p + FORWARD_OPTIONAL_WIDGET_ARGUMENT); + }]; + } +}; + +QT_END_NAMESPACE + +#endif // QMACSTYLE_P_H diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index 3e7e01d8048..e3ff518b4e1 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -214,7 +214,6 @@ QCocoaTheme::QCocoaTheme() : m_systemPalette(nullptr) { m_appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] { - NSAppearance.currentAppearance = NSApp.effectiveAppearance; handleSystemThemeChange(); }); @@ -291,13 +290,23 @@ QPlatformSystemTrayIcon *QCocoaTheme::createPlatformSystemTrayIcon() const const QPalette *QCocoaTheme::palette(Palette type) const { + // Note: NSColor resolves its RGB values based on the current + // drawing appearance, so we need to propagate the effective + // appearance when (re)creating the palettes. + if (type == SystemPalette) { - if (!m_systemPalette) - m_systemPalette = qt_mac_createSystemPalette(); + if (!m_systemPalette) { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + m_systemPalette = qt_mac_createSystemPalette(); + }]; + } return m_systemPalette; } else { - if (m_palettes.isEmpty()) - m_palettes = qt_mac_createRolePalettes(); + if (m_palettes.isEmpty()) { + [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ + m_palettes = qt_mac_createRolePalettes(); + }]; + } return m_palettes.value(type, nullptr); } return nullptr; diff --git a/src/plugins/styles/mac/main.mm b/src/plugins/styles/mac/main.mm index 5f4fbd9eb8e..ef6ca46045d 100644 --- a/src/plugins/styles/mac/main.mm +++ b/src/plugins/styles/mac/main.mm @@ -20,7 +20,7 @@ QStyle *QMacStylePlugin::create(const QString &key) { QMacAutoReleasePool pool; if (key.compare(QLatin1String("macos"), Qt::CaseInsensitive) == 0) - return new QMacStyle(); + return QMacStyle::create(); return 0; } diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index bc411eb72d8..83b2d284e86 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -41,6 +41,8 @@ #include <QtWidgets/qwizard.h> #endif +#include <QtGui/private/qmacstyle_p.h> + #include <cmath> QT_USE_NAMESPACE @@ -338,40 +340,6 @@ static const int toolButtonArrowMargin = 2; static const qreal focusRingWidth = 3.5; -// An application can force 'Aqua' theme while the system theme is one of -// the 'Dark' variants. Since in Qt we sometimes use NSControls and even -// NSCells directly without attaching them to any view hierarchy, we have -// to set NSAppearance.currentAppearance to 'Aqua' manually, to make sure -// the correct rendering path is triggered. Apple recommends us to un-set -// the current appearance back after we finished with drawing. This is what -// AppearanceSync is for. - -class AppearanceSync { -public: - AppearanceSync() - { - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave - && !isDarkMode()) { - auto requiredAppearanceName = NSApplication.sharedApplication.effectiveAppearance.name; - if (![NSAppearance.currentAppearance.name isEqualToString:requiredAppearanceName]) { - previous = NSAppearance.currentAppearance; - NSAppearance.currentAppearance = [NSAppearance appearanceNamed:requiredAppearanceName]; - } - } - } - - ~AppearanceSync() - { - if (previous) - NSAppearance.currentAppearance = previous; - } - -private: - NSAppearance *previous = nil; - - Q_DISABLE_COPY(AppearanceSync) -}; - static bool setupScroller(NSScroller *scroller, const QStyleOptionSlider *sb) { const qreal length = sb->maximum - sb->minimum + sb->pageStep; @@ -1995,6 +1963,11 @@ void QMacStylePrivate::resolveCurrentNSView(QWindow *window) const backingStoreNSView = window ? (NSView *)window->winId() : nil; } +QMacStyle *QMacStyle::create() +{ + return new QMacApperanceStyle<QMacStyle>; +} + QMacStyle::QMacStyle() : QCommonStyle(*new QMacStylePrivate) { @@ -2886,7 +2859,6 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai const QWidget *w) const { Q_D(const QMacStyle); - const AppearanceSync appSync; QMacCGContext cg(p); QWindow *window = w && w->window() ? w->window()->windowHandle() : nullptr; d->resolveCurrentNSView(window); @@ -3414,7 +3386,6 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter const QWidget *w) const { Q_D(const QMacStyle); - const AppearanceSync sync; const QMacAutoReleasePool pool; QMacCGContext cg(p); QWindow *window = w && w->window() ? w->window()->windowHandle() : nullptr; @@ -5086,7 +5057,6 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex const QWidget *widget) const { Q_D(const QMacStyle); - const AppearanceSync sync; QMacCGContext cg(p); QWindow *window = widget && widget->window() ? widget->window()->windowHandle() : nullptr; d->resolveCurrentNSView(window); diff --git a/src/plugins/styles/mac/qmacstyle_mac_p.h b/src/plugins/styles/mac/qmacstyle_mac_p.h index 99c8d1fec42..15ced48e21d 100644 --- a/src/plugins/styles/mac/qmacstyle_mac_p.h +++ b/src/plugins/styles/mac/qmacstyle_mac_p.h @@ -28,10 +28,13 @@ class QMacStylePrivate; class QMacStyle : public QCommonStyle { Q_OBJECT -public: +protected: QMacStyle(); +public: virtual ~QMacStyle(); + static QMacStyle *create(); + void polish(QWidget *w); void unpolish(QWidget *w); |