diff options
-rw-r--r-- | examples/widgets/gallery/main.cpp | 1 | ||||
-rw-r--r-- | examples/widgets/gallery/widgetgallery.cpp | 24 | ||||
-rw-r--r-- | src/gui/kernel/qplatformtheme.cpp | 5 | ||||
-rw-r--r-- | src/gui/kernel/qplatformtheme.h | 1 | ||||
-rw-r--r-- | src/gui/kernel/qstylehints.cpp | 49 | ||||
-rw-r--r-- | src/gui/kernel/qstylehints.h | 5 | ||||
-rw-r--r-- | src/plugins/platforms/android/qandroidplatformtheme.cpp | 15 | ||||
-rw-r--r-- | src/plugins/platforms/android/qandroidplatformtheme.h | 3 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoatheme.h | 1 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoatheme.mm | 17 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiostheme.h | 5 | ||||
-rw-r--r-- | src/plugins/platforms/ios/qiostheme.mm | 35 | ||||
-rw-r--r-- | src/plugins/platforms/ios/quiwindow.mm | 9 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowstheme.cpp | 76 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowstheme.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowswindow.cpp | 9 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp | 118 |
17 files changed, 342 insertions, 35 deletions
diff --git a/examples/widgets/gallery/main.cpp b/examples/widgets/gallery/main.cpp index cfac821209..2677b3708c 100644 --- a/examples/widgets/gallery/main.cpp +++ b/examples/widgets/gallery/main.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include <QApplication> +#include <QStyleHints> #include "widgetgallery.h" diff --git a/examples/widgets/gallery/widgetgallery.cpp b/examples/widgets/gallery/widgetgallery.cpp index 2de46419d9..d38dcbd5e8 100644 --- a/examples/widgets/gallery/widgetgallery.cpp +++ b/examples/widgets/gallery/widgetgallery.cpp @@ -26,6 +26,7 @@ #include <QSpinBox> #include <QStandardItemModel> #include <QStyle> +#include <QStyleHints> #include <QStyleFactory> #include <QTextBrowser> #include <QTreeView> @@ -135,6 +136,21 @@ WidgetGallery::WidgetGallery(QWidget *parent) auto styleLabel = createWidget1<QLabel>(tr("&Style:"), "styleLabel"); styleLabel->setBuddy(styleComboBox); + auto colorSchemeComboBox = createWidget<QComboBox>("colorSchemeComboBox"); + colorSchemeComboBox->addItem(tr("Auto")); + colorSchemeComboBox->addItem(tr("Light")); + colorSchemeComboBox->addItem(tr("Dark")); + // override the color scheme to dark + qApp->styleHints()->setColorScheme(Qt::ColorScheme::Dark); + colorSchemeComboBox->setCurrentIndex(2); + + auto colorSchemeLabel = createWidget1<QLabel>(tr("&Color Scheme:"), "colorSchemeLabel"); + colorSchemeLabel->setBuddy(colorSchemeComboBox); + + connect(colorSchemeComboBox, &QComboBox::currentIndexChanged, this, [](int index){ + QGuiApplication::styleHints()->setColorScheme(static_cast<Qt::ColorScheme>(index)); + }); + auto helpLabel = createWidget1<QLabel>(tr("Press F1 over a widget to see Documentation"), "helpLabel"); auto disableWidgetsCheckBox = createWidget1<QCheckBox>(tr("&Disable widgets"), "disableWidgetsCheckBox"); @@ -156,8 +172,12 @@ WidgetGallery::WidgetGallery(QWidget *parent) simpleInputWidgetsGroupBox, &QWidget::setDisabled); auto topLayout = new QHBoxLayout; - topLayout->addWidget(styleLabel); - topLayout->addWidget(styleComboBox); + auto appearanceLayout = new QGridLayout; + appearanceLayout->addWidget(styleLabel, 0, 0); + appearanceLayout->addWidget(styleComboBox, 0, 1); + appearanceLayout->addWidget(colorSchemeLabel, 1, 0); + appearanceLayout->addWidget(colorSchemeComboBox, 1, 1); + topLayout->addLayout(appearanceLayout); topLayout->addStretch(1); topLayout->addWidget(helpLabel); topLayout->addStretch(1); diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp index 48978b849a..3d1319615e 100644 --- a/src/gui/kernel/qplatformtheme.cpp +++ b/src/gui/kernel/qplatformtheme.cpp @@ -447,6 +447,11 @@ Qt::ColorScheme QPlatformTheme::colorScheme() const return Qt::ColorScheme::Unknown; } +void QPlatformTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + Q_UNUSED(scheme); +} + const QPalette *QPlatformTheme::palette(Palette type) const { Q_D(const QPlatformTheme); diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h index c0193947b9..d007a19675 100644 --- a/src/gui/kernel/qplatformtheme.h +++ b/src/gui/kernel/qplatformtheme.h @@ -295,6 +295,7 @@ public: #endif virtual Qt::ColorScheme colorScheme() const; + virtual void requestColorScheme(Qt::ColorScheme scheme); virtual const QPalette *palette(Palette type = SystemPalette) const; diff --git a/src/gui/kernel/qstylehints.cpp b/src/gui/kernel/qstylehints.cpp index 5029701f24..0d15d114ec 100644 --- a/src/gui/kernel/qstylehints.cpp +++ b/src/gui/kernel/qstylehints.cpp @@ -123,8 +123,29 @@ int QStyleHints::touchDoubleTapDistance() const /*! \property QStyleHints::colorScheme - \brief the color scheme of the platform theme. - \sa Qt::ColorScheme + \brief the color scheme used by the application. + + By default, this follows the system's default color scheme (also known as appearance), + and changes when the system color scheme changes (e.g. during dusk or dawn). + Setting the color scheme to an explicit value will override the system setting and + ignore any changes to the system's color scheme. However, doing so is a hint to the + system, and overriding the color scheme is not supported on all platforms. + + Resetting this property, or setting it to \l{Qt::ColorScheme::Unknown}, will remove + the override and make the application follow the system default again. The property + value will change to the color scheme the system currently has. + + When this property changes, Qt will read the system palette and update the default + palette, but won't overwrite palette entries that have been explicitly set by the + application. When the colorSchemeChange() signal gets emitted, the old palette is + still in effect. + + Application-specific colors should be selected to work well with the effective + palette, taking the current color scheme into account. To update application- + specific colors when the effective palette changes, handle + \l{QEvent::}{PaletteChange} or \l{QEvent::}{ApplicationPaletteChange} events. + + \sa Qt::ColorScheme, QGuiApplication::palette(), QEvent::PaletteChange \since 6.5 */ Qt::ColorScheme QStyleHints::colorScheme() const @@ -134,6 +155,30 @@ Qt::ColorScheme QStyleHints::colorScheme() const } /*! + \since 6.8 + + Sets the color scheme used by the application to an explicit \a scheme, or + revert to the system's current color scheme if \a scheme is Qt::ColorScheme::Unknown. +*/ +void QStyleHints::setColorScheme(Qt::ColorScheme scheme) +{ + if (!QCoreApplication::instance()) { + qWarning("Must construct a QGuiApplication before accessing a platform theme hint."); + return; + } + if (QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) + theme->requestColorScheme(scheme); +} + +/*! + \fn void QStyleHints::unsetColorScheme() + \since 6.8 + + Restores the color scheme to the system's current color scheme. +*/ + + +/*! Sets the \a mousePressAndHoldInterval. \internal \sa mousePressAndHoldInterval() diff --git a/src/gui/kernel/qstylehints.h b/src/gui/kernel/qstylehints.h index 969bec4b35..8981383060 100644 --- a/src/gui/kernel/qstylehints.h +++ b/src/gui/kernel/qstylehints.h @@ -52,7 +52,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject Q_PROPERTY(int mouseDoubleClickDistance READ mouseDoubleClickDistance STORED false CONSTANT FINAL) Q_PROPERTY(int touchDoubleTapDistance READ touchDoubleTapDistance STORED false CONSTANT FINAL) - Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme NOTIFY colorSchemeChanged FINAL) + Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme WRITE setColorScheme + RESET unsetColorScheme NOTIFY colorSchemeChanged FINAL) public: void setMouseDoubleClickInterval(int mouseDoubleClickInterval); @@ -94,6 +95,8 @@ public: void setMouseQuickSelectionThreshold(int threshold); int mouseQuickSelectionThreshold() const; Qt::ColorScheme colorScheme() const; + void setColorScheme(Qt::ColorScheme scheme); + void unsetColorScheme() { setColorScheme(Qt::ColorScheme::Unknown); } Q_SIGNALS: void cursorFlashTimeChanged(int cursorFlashTime); diff --git a/src/plugins/platforms/android/qandroidplatformtheme.cpp b/src/plugins/platforms/android/qandroidplatformtheme.cpp index 7b9072df69..99eeabac1d 100644 --- a/src/plugins/platforms/android/qandroidplatformtheme.cpp +++ b/src/plugins/platforms/android/qandroidplatformtheme.cpp @@ -161,7 +161,10 @@ QJsonObject AndroidStyle::loadStyleData() if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar)) stylePath += slashChar; - if (QAndroidPlatformIntegration::colorScheme() == Qt::ColorScheme::Dark) + const Qt::ColorScheme colorScheme = QAndroidPlatformTheme::instance() + ? QAndroidPlatformTheme::instance()->colorScheme() + : QAndroidPlatformIntegration::colorScheme(); + if (colorScheme == Qt::ColorScheme::Dark) stylePath += "darkUiMode/"_L1; Q_ASSERT(!stylePath.isEmpty()); @@ -423,9 +426,19 @@ void QAndroidPlatformTheme::showPlatformMenuBar() Qt::ColorScheme QAndroidPlatformTheme::colorScheme() const { + if (m_colorSchemeOverride != Qt::ColorScheme::Unknown) + return m_colorSchemeOverride; return QAndroidPlatformIntegration::colorScheme(); } +void QAndroidPlatformTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + m_colorSchemeOverride = scheme; + QMetaObject::invokeMethod(qGuiApp, [this]{ + updateColorScheme(); + }); +} + static inline int paletteType(QPlatformTheme::Palette type) { switch (type) { diff --git a/src/plugins/platforms/android/qandroidplatformtheme.h b/src/plugins/platforms/android/qandroidplatformtheme.h index ce3d6d5f73..1b4ab5664d 100644 --- a/src/plugins/platforms/android/qandroidplatformtheme.h +++ b/src/plugins/platforms/android/qandroidplatformtheme.h @@ -40,6 +40,8 @@ public: QPlatformMenuItem *createPlatformMenuItem() const override; void showPlatformMenuBar() override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; + const QPalette *palette(Palette type = SystemPalette) const override; const QFont *font(Font type = SystemFont) const override; QIconEngine *createIconEngine(const QString &iconName) const override; @@ -57,6 +59,7 @@ private: std::shared_ptr<AndroidStyle> m_androidStyleData; QPalette m_defaultPalette; QFont m_systemFont; + Qt::ColorScheme m_colorSchemeOverride = Qt::ColorScheme::Unknown; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h index c49d83feae..97e0f633a7 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.h +++ b/src/plugins/platforms/cocoa/qcocoatheme.h @@ -44,6 +44,7 @@ public: static const char *name; + void requestColorScheme(Qt::ColorScheme scheme) override; void handleSystemThemeChange(); #ifndef QT_NO_SHORTCUT diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index f4fbfadbe4..d9135c76c8 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -478,6 +478,23 @@ Qt::ColorScheme QCocoaTheme::colorScheme() const return m_colorScheme; } +void QCocoaTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + NSAppearance *appearance = nil; + switch (scheme) { + case Qt::ColorScheme::Dark: + appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; + break; + case Qt::ColorScheme::Light: + appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; + break; + case Qt::ColorScheme::Unknown: + break; + } + if (appearance != NSApp.effectiveAppearance) + NSApplication.sharedApplication.appearance = appearance; +} + /* Update the theme's color scheme based on the current appearance. diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h index f0a404a61a..70e2c37ff1 100644 --- a/src/plugins/platforms/ios/qiostheme.h +++ b/src/plugins/platforms/ios/qiostheme.h @@ -4,6 +4,8 @@ #ifndef QIOSTHEME_H #define QIOSTHEME_H +#import <UIKit/UIKit.h> + #include <QtCore/QHash> #include <QtGui/QPalette> #include <qpa/qplatformtheme.h> @@ -22,6 +24,7 @@ public: QVariant themeHint(ThemeHint hint) const override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; #if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS) QPlatformMenuItem* createPlatformMenuItem() const override; @@ -37,9 +40,11 @@ public: static const char *name; static void initializeSystemPalette(); + static void applyTheme(UIWindow *window); private: static QPalette s_systemPalette; + static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown; QMacNotificationObserver m_contentSizeCategoryObserver; }; diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm index 3853de9cf1..0b420a875a 100644 --- a/src/plugins/platforms/ios/qiostheme.mm +++ b/src/plugins/platforms/ios/qiostheme.mm @@ -154,6 +154,9 @@ Qt::ColorScheme QIOSTheme::colorScheme() const // the OS reports itself as always being in dark mode. return Qt::ColorScheme::Dark; #else + if (s_colorSchemeOverride != Qt::ColorScheme::Unknown) + return s_colorSchemeOverride; + // Set the appearance based on the QUIWindow // Fallback to the UIScreen if no window is created yet UIUserInterfaceStyle appearance = UIScreen.mainScreen.traitCollection.userInterfaceStyle; @@ -171,6 +174,38 @@ Qt::ColorScheme QIOSTheme::colorScheme() const #endif } +void QIOSTheme::requestColorScheme(Qt::ColorScheme scheme) +{ +#if defined(Q_OS_VISIONOS) + Q_UNUSED(scheme); +#else + s_colorSchemeOverride = scheme; + + const NSArray<UIWindow *> *windows = qt_apple_sharedApplication().windows; + for (UIWindow *window in windows) { + // don't apply a theme to windows we don't own + if (qt_objc_cast<QUIWindow*>(window)) + applyTheme(window); + } +#endif +} + +void QIOSTheme::applyTheme(UIWindow *window) +{ + const UIUserInterfaceStyle style = []{ + switch (s_colorSchemeOverride) { + case Qt::ColorScheme::Dark: + return UIUserInterfaceStyleDark; + case Qt::ColorScheme::Light: + return UIUserInterfaceStyleLight; + case Qt::ColorScheme::Unknown: + return UIUserInterfaceStyleUnspecified; + } + }(); + + window.overrideUserInterfaceStyle = style; +} + const QFont *QIOSTheme::font(Font type) const { const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); diff --git a/src/plugins/platforms/ios/quiwindow.mm b/src/plugins/platforms/ios/quiwindow.mm index 7c910b6d9e..783e243e10 100644 --- a/src/plugins/platforms/ios/quiwindow.mm +++ b/src/plugins/platforms/ios/quiwindow.mm @@ -22,6 +22,15 @@ return self; } +- (instancetype)initWithWindowScene:(UIWindowScene *)windowScene +{ + if ((self = [super initWithWindowScene:windowScene])) + self->_sendingEvent = NO; + + QIOSTheme::applyTheme(self); + return self; +} + - (void)sendEvent:(UIEvent *)event { QScopedValueRollback<BOOL> sendingEvent(self->_sendingEvent, YES); diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index e8a324aedb..b6017c7692 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -301,6 +301,9 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result) void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) { + QColor foreground, background, + accent, accentDark, accentDarker, accentDarkest, + accentLight, accentLighter, accentLightest; #if QT_CONFIG(cpp_winrt) using namespace winrt::Windows::UI::ViewManagement; const auto settings = UISettings(); @@ -308,32 +311,37 @@ void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result) // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API // returns the old system colors, not the dark mode colors. If the background is black (which it // usually), then override it with a dark gray instead so that we can go up and down the lightness. - const QColor foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); - const QColor background = [&settings]() -> QColor { - auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); - if (systemBackground == Qt::black) - systemBackground = QColor(0x1E, 0x1E, 0x1E); - return systemBackground; - }(); - - const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); - const QColor accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); - const QColor accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); - const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); - const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); - const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2)); - const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3)); -#else - const QColor foreground = Qt::white; - const QColor background = QColor(0x1E, 0x1E, 0x1E); - const QColor accent = qt_accentColor(AccentColorNormal); - const QColor accentDark = accent.darker(120); - const QColor accentDarker = accentDark.darker(120); - const QColor accentDarkest = accentDarker.darker(120); - const QColor accentLight = accent.lighter(120); - const QColor accentLighter = accentLight.lighter(120); - const QColor accentLightest = accentLighter.lighter(120); + if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) { + // the system is actually running in dark mode, so UISettings will give us dark colors + foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground)); + background = [&settings]() -> QColor { + auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background)); + if (systemBackground == Qt::black) + systemBackground = QColor(0x1E, 0x1E, 0x1E); + return systemBackground; + }(); + + accent = getSysColor(settings.GetColorValue(UIColorType::Accent)); + accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1)); + accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2)); + accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3)); + accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1)); + accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2)); + accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3)); + } else #endif + { + // If the system is running in light mode, then we need to make up our own dark palette + foreground = Qt::white; + background = QColor(0x1E, 0x1E, 0x1E); + accent = qt_accentColor(AccentColorNormal); + accentDark = accent.darker(120); + accentDarker = accentDark.darker(120); + accentDarkest = accentDarker.darker(120); + accentLight = accent.lighter(120); + accentLighter = accentLight.lighter(120); + accentLightest = accentLighter.lighter(120); + } const QColor linkColor = accent; const QColor buttonColor = background.lighter(200); @@ -549,13 +557,25 @@ Qt::ColorScheme QWindowsTheme::effectiveColorScheme() { if (queryHighContrast()) return Qt::ColorScheme::Unknown; - return s_colorScheme; + if (s_colorSchemeOverride != Qt::ColorScheme::Unknown) + return s_colorSchemeOverride; + if (s_colorScheme != Qt::ColorScheme::Unknown) + return s_colorScheme; + return queryColorScheme(); +} + +void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme) +{ + s_colorSchemeOverride = scheme; + handleSettingsChanged(); } void QWindowsTheme::handleSettingsChanged() { - const auto newColorScheme = QWindowsTheme::queryColorScheme(); - const bool colorSchemeChanged = newColorScheme != QWindowsTheme::s_colorScheme; + const auto oldColorScheme = s_colorScheme; + s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry + const auto newColorScheme = effectiveColorScheme(); + const bool colorSchemeChanged = newColorScheme != oldColorScheme; s_colorScheme = newColorScheme; auto integration = QWindowsIntegration::instance(); integration->updateApplicationBadge(); diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index 6109122944..96ae6197b4 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -32,6 +32,8 @@ public: QVariant themeHint(ThemeHint) const override; Qt::ColorScheme colorScheme() const override; + void requestColorScheme(Qt::ColorScheme scheme) override; + Qt::ColorScheme requestedColorScheme() const { return s_colorSchemeOverride; } static void handleSettingsChanged(); @@ -79,6 +81,8 @@ private: static QWindowsTheme *m_instance; static inline Qt::ColorScheme s_colorScheme = Qt::ColorScheme::Unknown; + static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown; + QPalette *m_palettes[NPalettes]; QFont *m_fonts[NFonts]; QList<QSize> m_fileIconSizes; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 5d96d40af5..ee0b88ba54 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -850,10 +850,17 @@ static inline bool shouldApplyDarkFrame(const QWindow *w) { if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) return false; - // the application has explicitly opted out of dark frames + + // the user of the application has explicitly opted out of dark frames if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) return false; + // the application explicitly overrides the color scheme + if (const auto requestedColorScheme = QWindowsTheme::instance()->requestedColorScheme(); + requestedColorScheme != Qt::ColorScheme::Unknown) { + return requestedColorScheme == Qt::ColorScheme::Dark; + } + // if the application supports a dark border, and the palette is dark (window background color // is darker than the text), then turn dark-border support on, otherwise use a light border. auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w)); diff --git a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp index dfc138d8fc..2eb2d84504 100644 --- a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp +++ b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp @@ -23,6 +23,7 @@ #include <QtGui/QFontDatabase> #include <QtGui/QClipboard> +#include <QtGui/QStyleHints> #include <QtWidgets/QApplication> #include <QtWidgets/QMessageBox> @@ -119,6 +120,7 @@ private slots: void style(); void applicationPalettePolish(); + void setColorScheme(); void allWidgets(); void topLevelWidgets(); @@ -2051,6 +2053,122 @@ void tst_QApplication::applicationPalettePolish() } } +void tst_QApplication::setColorScheme() +{ + int argc = 1; + QApplication app(argc, &argv0); + + if (QStringList{"minimal", "offscreen", "wayland", "xcb", "wasm", "webassembly"} + .contains(QGuiApplication::platformName(), Qt::CaseInsensitive)) { + QSKIP("Setting the colorScheme is not implemented on this platform."); + } + qDebug() << "Testing setColorScheme on platform" << QGuiApplication::platformName(); + + if (QByteArrayView(app.style()->metaObject()->className()) == "QWindowsVistaStyle") + QSKIP("Setting the colorScheme is not supported with the Windows Vista style."); + + const Qt::ColorScheme defaultColorScheme = QApplication::styleHints()->colorScheme(); + // if we implement setColorScheme, then we must be able to read it + QVERIFY(defaultColorScheme != Qt::ColorScheme::Unknown); + const Qt::ColorScheme newColorScheme = defaultColorScheme == Qt::ColorScheme::Light + ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; + + class TopLevelWidget : public QWidget + { + QList<QEvent::Type> events; + public: + TopLevelWidget() + { + setObjectName("colorScheme TopLevelWidget"); + } + + void clearEvents() + { + events.clear(); + } + qsizetype eventCount(QEvent::Type type) const + { + return events.count(type); + } + protected: + bool event(QEvent *event) override + { + switch (event->type()) { + case QEvent::ApplicationPaletteChange: + case QEvent::PaletteChange: + case QEvent::ThemeChange: + events << event->type(); + break; + default: + break; + } + + return QWidget::event(event); + } + } topLevelWidget; + topLevelWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&topLevelWidget)); + + QSignalSpy colorSchemeChangedSpy(app.styleHints(), &QStyleHints::colorSchemeChanged); + + // always start with a clean list + topLevelWidget.clearEvents(); + const QPalette defaultPalette = topLevelWidget.palette(); + + bool oldPaletteWhenSchemeChanged = false; + connect(app.styleHints(), &QStyleHints::colorSchemeChanged, this, + [defaultPalette, &topLevelWidget, &oldPaletteWhenSchemeChanged]{ + oldPaletteWhenSchemeChanged = defaultPalette == topLevelWidget.palette(); + }); + + app.styleHints()->setColorScheme(newColorScheme); + QTRY_COMPARE(colorSchemeChangedSpy.count(), 1); + // We have not yet updated the palette when we emit the colorSchemeChanged + // signal, so the toplevel widget should still use the previous palette + QVERIFY(oldPaletteWhenSchemeChanged); + QCOMPARE(topLevelWidget.eventCount(QEvent::ThemeChange), 1); + // We can't guarantee that there is only one ApplicationPaletteChange, + // and they might arrive asynchronously in response to ThemeChange + QTRY_COMPARE_GE(topLevelWidget.eventCount(QEvent::ApplicationPaletteChange), 1); + // But we can guarantee a single PaletteChange event for the widget + QCOMPARE(topLevelWidget.eventCount(QEvent::PaletteChange), 1); + // The palette should have changed + QCOMPARE_NE(topLevelWidget.palette(), defaultPalette); + + topLevelWidget.clearEvents(); + colorSchemeChangedSpy.clear(); + + // verify that a widget shown with a color scheme override in place respect that + QWidget newWidget; + newWidget.show(); + QVERIFY(QTest::qWaitForWindowExposed(&newWidget)); + QCOMPARE(newWidget.palette(), topLevelWidget.palette()); + + // Setting to Unknown should follow the system preference again + app.styleHints()->setColorScheme(Qt::ColorScheme::Unknown); + QTRY_COMPARE(colorSchemeChangedSpy.count(), 1); + QCOMPARE(app.styleHints()->colorScheme(), defaultColorScheme); + QTRY_COMPARE(topLevelWidget.eventCount(QEvent::PaletteChange), 1); + + auto debugPalette = qScopeGuard([defaultPalette, &topLevelWidget]{ + qDebug() << "Inspecting palettes for differences"; + const QPalette palette = topLevelWidget.palette(); + for (int g = 0; g < QPalette::NColorGroups; ++g) { + for (int r = 0; r < QPalette::NColorRoles; ++r) { + const auto group = static_cast<QPalette::ColorGroup>(g); + const auto role = static_cast<QPalette::ColorRole>(r); + qDebug() << "...Checking" << group << role; + const auto actualBrush = palette.brush(group, role); + const auto expectedBrush = defaultPalette.brush(group, role); + if (palette.brush(group, role) != defaultPalette.brush(group, role)) + qWarning() << "...Difference in" << group << role << actualBrush << expectedBrush; + } + } + }); + QCOMPARE(topLevelWidget.palette(), defaultPalette); + debugPalette.dismiss(); +} + void tst_QApplication::allWidgets() { int argc = 1; |