diff options
author | MohammadHossein Qanbari <[email protected]> | 2024-07-15 13:15:01 +0200 |
---|---|---|
committer | MohammadHossein Qanbari <[email protected]> | 2024-07-23 15:43:50 +0200 |
commit | b45629207ee32a3d80b6ea6553e8762eec8a86da (patch) | |
tree | f48d40e017bf66b34c7c20670b7bd4bd2a133530 | |
parent | ef9aa1da98411cd80d5a752839c204c2fe75afa3 (diff) |
Material Style: update style theme when system theme is changed
The Material style did not use the system theme when it was set to
QQuickMaterialStyle::System. The problem was that QQuickMaterialStyle
does not know about system theme changes.
To make this possible, it is required to implement the
QtQuickControls2MaterialStylePlugin::updateTheme() method. This method
overrides the QQuickStylePlugin::updateTheme() virtual method, and it is
when the system theme is changed. The material style plugin has access
to the QQuickMaterialTheme. Then, the material theme will be notified
when the system theme is changed.
The material theme did not have access to the material styles as they
are attached to the QML objects in the QML engine. To address this, when
a material style's theme is set to System, that material style can
register itself to the list of the system styles that are stored in the
material theme.
When the system theme is changed, the material style plugin notifies the
material theme and the material theme iterates through the material
system styles and updates their themes.
To prevent dangling pointer issues, the material styles list (with
System theme) uses QPointer type to detect null pointers after they are
destructed. They will be removed from the list when they are touched
and are null.
The testcase creates an ApplicationWindow with value of Material.System
for Material.theme property to follow system theme changes. It then
toggles the platform theme through the TestHandler and compares the
results. The TestHandler class creates a mocked class called
MockPlatformTheme that inherits from the QPlatformTheme class and
overrides the colorScheme() and requestColorScheme() virtual functions.
The reason for overriding these methods is to simulate the platform
theme change event.
[ChangeLog][Controls][Material] If the Material.theme is set to
Material.System, the application theme changes when the system theme is
changed. This also works for the child attached styles. If its theme is
set to Material.System, regardless of its attached parent style, it will
follow the system theme changes.
Fixes: QTBUG-127169
Pick-to: 6.8
Change-Id: I29a0c59525f342595a20a908faa85bcae6615bf4
Reviewed-by: Mitch Curtis <[email protected]>
8 files changed, 197 insertions, 11 deletions
diff --git a/src/quickcontrols/material/qquickmaterialstyle.cpp b/src/quickcontrols/material/qquickmaterialstyle.cpp index f8ced5821b..4d8a6696c7 100644 --- a/src/quickcontrols/material/qquickmaterialstyle.cpp +++ b/src/quickcontrols/material/qquickmaterialstyle.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qquickmaterialstyle_p.h" +#include "qquickmaterialtheme_p.h" #include <QtCore/qdebug.h> #if QT_CONFIG(settings) @@ -441,7 +442,8 @@ QQuickMaterialStyle::QQuickMaterialStyle(QObject *parent) : QQuickAttachedProper m_customBackground(globalBackgroundCustom), m_hasForeground(hasGlobalForeground), m_hasBackground(hasGlobalBackground), - m_theme(globalTheme), + m_systemTheme(globalTheme == System), + m_theme(effectiveTheme(globalTheme)), m_primary(globalPrimary), m_accent(globalAccent), m_foreground(globalForeground), @@ -462,14 +464,26 @@ QQuickMaterialStyle::Theme QQuickMaterialStyle::theme() const void QQuickMaterialStyle::setTheme(Theme theme) { - if (theme == System) - theme = QQuickStylePrivate::isDarkSystemTheme() ? Dark : Light; - m_explicitTheme = true; - if (m_theme == theme) + + // If theme is System: m_theme is set to system's theme (Dark/Light) + // and m_systemTheme is set to true. + // If theme is Dark/Light: m_theme is set to the input theme (Dark/Light) + // and m_systemTheme is set to false. + const bool systemThemeChanged = (m_systemTheme != (theme == System)); + // Check m_theme and m_systemTheme are changed. + if ((m_theme == effectiveTheme(theme)) && !systemThemeChanged) return; - m_theme = theme; + m_theme = effectiveTheme(theme); + m_systemTheme = (theme == System); + if (systemThemeChanged) { + if (m_systemTheme) + QQuickMaterialTheme::registerSystemStyle(this); + else + QQuickMaterialTheme::unregisterSystemStyle(this); + } + propagateTheme(); themeChange(); if (!m_customAccent) @@ -482,10 +496,14 @@ void QQuickMaterialStyle::setTheme(Theme theme) void QQuickMaterialStyle::inheritTheme(Theme theme) { - if (m_explicitTheme || m_theme == theme) + const bool systemThemeChanged = (m_systemTheme != (theme == System)); + const bool themeChanged = systemThemeChanged || (m_theme != effectiveTheme(theme)); + if (m_explicitTheme || !themeChanged) return; - m_theme = theme; + m_theme = effectiveTheme(theme); + m_systemTheme = (theme == System); + propagateTheme(); themeChange(); if (!m_customAccent) @@ -502,7 +520,10 @@ void QQuickMaterialStyle::propagateTheme() for (QQuickAttachedPropertyPropagator *child : styles) { QQuickMaterialStyle *material = qobject_cast<QQuickMaterialStyle *>(child); if (material) - material->inheritTheme(m_theme); + // m_theme is the effective theme, either Dark or Light. + // m_systemTheme indicates whether the theme is set by + // the system (true) or manually (false). + material->inheritTheme(m_systemTheme ? System : m_theme); } } @@ -1424,7 +1445,7 @@ void QQuickMaterialStyle::initGlobals() QByteArray themeValue = resolveSetting("QT_QUICK_CONTROLS_MATERIAL_THEME", settings, QStringLiteral("Theme")); Theme themeEnum = toEnumValue<Theme>(themeValue, &ok); if (ok) - globalTheme = effectiveTheme(themeEnum); + globalTheme = themeEnum; else if (!themeValue.isEmpty()) qWarning().nospace().noquote() << "Material: unknown theme value: " << themeValue; diff --git a/src/quickcontrols/material/qquickmaterialstyle_p.h b/src/quickcontrols/material/qquickmaterialstyle_p.h index f7c2b256ba..fd2cdfeb16 100644 --- a/src/quickcontrols/material/qquickmaterialstyle_p.h +++ b/src/quickcontrols/material/qquickmaterialstyle_p.h @@ -350,6 +350,7 @@ private: bool m_hasForeground = false; bool m_hasBackground = false; // The actual values for this item, whether explicit, inherited or globally set. + bool m_systemTheme = false; Theme m_theme = Light; uint m_primary = 0; uint m_accent = 0; diff --git a/src/quickcontrols/material/qquickmaterialtheme.cpp b/src/quickcontrols/material/qquickmaterialtheme.cpp index d121a1a47e..ed8d1e0ef9 100644 --- a/src/quickcontrols/material/qquickmaterialtheme.cpp +++ b/src/quickcontrols/material/qquickmaterialtheme.cpp @@ -8,9 +8,48 @@ #include <QtGui/qfont.h> #include <QtGui/qfontdatabase.h> #include <QtQuickTemplates2/private/qquicktheme_p.h> - +#include <QtCore/qmutex.h> QT_BEGIN_NAMESPACE +struct QQuickMaterialThemePrivate +{ + static inline void addSystemStyle(QPointer<QQuickMaterialStyle> style); + static inline void removeSystemStyle(QPointer<QQuickMaterialStyle> style); + static inline void updateSystemStyles(); + + static inline std::vector<QPointer<QQuickMaterialStyle>> systemStyles = {}; + static inline QMutex mutex; +}; + +void QQuickMaterialThemePrivate::addSystemStyle(QPointer<QQuickMaterialStyle> style) +{ + QMutexLocker locker{&mutex}; + auto it = std::find(systemStyles.begin(), systemStyles.end(), style); + if (it == systemStyles.end()) + systemStyles.push_back(style); +} + +void QQuickMaterialThemePrivate::removeSystemStyle(QPointer<QQuickMaterialStyle> style) +{ + QMutexLocker locker{&mutex}; + auto it = std::find(systemStyles.begin(), systemStyles.end(), style); + if (it != systemStyles.end()) + systemStyles.erase(it); +} + +void QQuickMaterialThemePrivate::updateSystemStyles() +{ + QMutexLocker locker{&mutex}; + for (auto it = systemStyles.begin(); it != systemStyles.end(); ) { + if (it->isNull()) { + it = systemStyles.erase(it); + } else { + (*it)->setTheme(QQuickMaterialStyle::System); + ++it; + } + } +} + void QQuickMaterialTheme::initialize(QQuickTheme *theme) { QFont systemFont; @@ -74,4 +113,19 @@ void QQuickMaterialTheme::initialize(QQuickTheme *theme) theme->setFont(QQuickTheme::SpinBox, editorFont); } +void QQuickMaterialTheme::registerSystemStyle(QQuickMaterialStyle *style) +{ + QQuickMaterialThemePrivate::addSystemStyle(QPointer{style}); +} + +void QQuickMaterialTheme::unregisterSystemStyle(QQuickMaterialStyle *style) +{ + QQuickMaterialThemePrivate::removeSystemStyle(QPointer{style}); +} + +void QQuickMaterialTheme::updateTheme() +{ + QQuickMaterialThemePrivate::updateSystemStyles(); +} + QT_END_NAMESPACE diff --git a/src/quickcontrols/material/qquickmaterialtheme_p.h b/src/quickcontrols/material/qquickmaterialtheme_p.h index bdaecd1a87..70356967c5 100644 --- a/src/quickcontrols/material/qquickmaterialtheme_p.h +++ b/src/quickcontrols/material/qquickmaterialtheme_p.h @@ -20,11 +20,15 @@ QT_BEGIN_NAMESPACE class QQuickTheme; +class QQuickMaterialStyle; class Q_QUICKCONTROLS2MATERIAL_EXPORT QQuickMaterialTheme { public: static void initialize(QQuickTheme *theme); + static void registerSystemStyle(QQuickMaterialStyle *style); + static void unregisterSystemStyle(QQuickMaterialStyle *style); + static void updateTheme(); }; QT_END_NAMESPACE diff --git a/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp b/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp index 4911a3e0f2..79ae0e2c5f 100644 --- a/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp +++ b/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp @@ -21,6 +21,7 @@ public: QString name() const override; void initializeTheme(QQuickTheme *theme) override; + void updateTheme() override; QQuickMaterialTheme theme; }; @@ -42,6 +43,11 @@ void QtQuickControls2MaterialStylePlugin::initializeTheme(QQuickTheme *theme) this->theme.initialize(theme); } +void QtQuickControls2MaterialStylePlugin::updateTheme() +{ + theme.updateTheme(); +} + QT_END_NAMESPACE #include "qtquickcontrols2materialstyleplugin.moc" diff --git a/tests/auto/quickcontrols/qquickmaterialstyle/CMakeLists.txt b/tests/auto/quickcontrols/qquickmaterialstyle/CMakeLists.txt index 0fe3fe7b9a..4a5f42ee82 100644 --- a/tests/auto/quickcontrols/qquickmaterialstyle/CMakeLists.txt +++ b/tests/auto/quickcontrols/qquickmaterialstyle/CMakeLists.txt @@ -26,6 +26,9 @@ qt_internal_add_test(tst_qquickmaterialstyle tst_qquickmaterialstyle.cpp LIBRARIES Qt::Gui + Qt::GuiPrivate + Qt::QuickControls2MaterialPrivate + Qt::QmlPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml b/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml index cdd8fb41e6..001567d3a9 100644 --- a/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml +++ b/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml @@ -9,6 +9,8 @@ import QtQuick.Controls import QtQuick.Controls.Material import QtQuick.Controls.Material.impl as MaterialImpl +import Qt.test + TestCase { id: testCase width: 200 @@ -1365,4 +1367,28 @@ TestCase { let headerItem = window.listView.headerItem compare(headerItem.Material.theme, Material.Dark) } + + Component { + id: systemThemeComponent + + ApplicationWindow { + width: 200 + height: 200 + visible: true + Material.theme: Material.System + } + } + + function test_systemTheme() { + let window = createTemporaryObject(systemThemeComponent, testCase) + verify(window) + + const toggleTheme = (theme) => (theme === Material.Dark) ? Material.Light : Material.Dark + + TestHelper.platformTheme = toggleTheme(TestHelper.platformTheme) + tryCompare(window.Material, "theme", TestHelper.platformTheme) + + TestHelper.platformTheme = toggleTheme(TestHelper.platformTheme) + tryCompare(window.Material, "theme", TestHelper.platformTheme) + } } diff --git a/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp b/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp index 48a3e2138a..b294b0d815 100644 --- a/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp +++ b/tests/auto/quickcontrols/qquickmaterialstyle/tst_qquickmaterialstyle.cpp @@ -1,17 +1,88 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtQuickControls2Material/private/qquickmaterialstyle_p.h> #include <QtQuickTest/quicktest.h> +namespace { +static inline Qt::ColorScheme toColorScheme(QQuickMaterialStyle::Theme theme) +{ + switch (theme) { + case QQuickMaterialStyle::Theme::System: + return Qt::ColorScheme::Unknown; + case QQuickMaterialStyle::Light: + return Qt::ColorScheme::Light; + case QQuickMaterialStyle::Dark: + return Qt::ColorScheme::Dark; + default: + Q_UNREACHABLE_RETURN(Qt::ColorScheme::Unknown); + } +} + +static inline QQuickMaterialStyle::Theme toMaterialTheme(Qt::ColorScheme colorScheme) +{ + switch (colorScheme) { + case Qt::ColorScheme::Unknown: + return QQuickMaterialStyle::System; + case Qt::ColorScheme::Light: + return QQuickMaterialStyle::Light; + case Qt::ColorScheme::Dark: + return QQuickMaterialStyle::Dark; + default: + Q_UNREACHABLE_RETURN(QQuickMaterialStyle::System); + } +} +} + + +class MockPlatformTheme : public QPlatformTheme +{ + Qt::ColorScheme colorScheme() const override + { + return m_colorScheme; + } + void requestColorScheme(Qt::ColorScheme theme) override + { + m_colorScheme = theme; + QWindowSystemInterfacePrivate::ThemeChangeEvent tce{nullptr}; + QGuiApplicationPrivate::processThemeChanged(&tce); + } + +private: + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; +}; + + class Setup : public QObject { Q_OBJECT + Q_PROPERTY(QQuickMaterialStyle::Theme platformTheme READ platformTheme WRITE setPlatformTheme CONSTANT FINAL) public slots: void applicationAvailable() { QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuWindows); } + + void qmlEngineAvailable(QQmlEngine*) + { + qmlRegisterSingletonInstance("Qt.test", 1, 0, "TestHelper", this); + + QGuiApplicationPrivate::platform_theme = new MockPlatformTheme; + } + +public: + void setPlatformTheme(QQuickMaterialStyle::Theme theme) + { + QGuiApplicationPrivate::platform_theme->requestColorScheme(toColorScheme(theme)); + } + + QQuickMaterialStyle::Theme platformTheme() const + { + return toMaterialTheme(QGuiApplicationPrivate::platform_theme->colorScheme()); + } }; QUICK_TEST_MAIN_WITH_SETUP(tst_qquickmaterialstyle, Setup) |