aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2025-11-19 15:49:47 +0100
committerRichard Moe Gustavsen <richard.gustavsen@qt.io>2025-11-27 12:22:49 +0100
commitc056aaa055187af7290d363d5ef02a4948cd8813 (patch)
treed5df750ffefe1d9dbb366177587a9b151d113c78 /src
parent8890b4a2d56ebdd51bf550d307f48d60ccf9a632 (diff)
StyleKit: add StyleKit to labs
This patch introduces a new styling API for Controls called StyleKit. StyleKit provides a higher-level, key–value–based approach to styling applications, serving as an alternative to working directly with the lower-level Templates API. The primary goal of StyleKit is to offer a unified API for styling both Controls and Widgets. The current Templates-based approach relies heavily on JavaScript, which makes it unsuitable for use with Widgets. This initial version supports Controls only; support for Widgets will be added in a future update. StyleKit is designed to make it easier for designers and UI developers to: - Focus on visual styling rather than Template logic (such as geometry, delegate positioning, and handle placement). - Allow style properties to propagate, enabling you to factor out common styling into shared control types and override only what differs in the more specific control types. - Style controls independently in each of their states, without needing nested ternary expressions to check state. - Define and apply multiple themes with minimal effort. - Provide different style variations depending on context. For example, styling a Switch differently when it appears inside a ToolBar. [ChangeLog][Qt labs] Introduced new QML module 'StyleKit'. StyleKit provides a flexible styling framework for Qt Quick Controls, enabling developers to define reusable styles and themes using a simple key-value property format. Change-Id: Iae25324486aea7a7b9b2ce52135327ec7e9b6f59 Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/labs/CMakeLists.txt4
-rw-r--r--src/labs/stylekit/Button.qml56
-rw-r--r--src/labs/stylekit/CMakeLists.txt59
-rw-r--r--src/labs/stylekit/CheckBox.qml87
-rw-r--r--src/labs/stylekit/ComboBox.qml135
-rw-r--r--src/labs/stylekit/Frame.qml35
-rw-r--r--src/labs/stylekit/ItemDelegate.qml57
-rw-r--r--src/labs/stylekit/Page.qml39
-rw-r--r--src/labs/stylekit/Pane.qml36
-rw-r--r--src/labs/stylekit/Popup.qml38
-rw-r--r--src/labs/stylekit/RadioButton.qml87
-rw-r--r--src/labs/stylekit/RangeSlider.qml135
-rw-r--r--src/labs/stylekit/Slider.qml87
-rw-r--r--src/labs/stylekit/SpinBox.qml140
-rw-r--r--src/labs/stylekit/Style.qml23
-rw-r--r--src/labs/stylekit/StyleKitAnimation.qml122
-rw-r--r--src/labs/stylekit/StyleKitDelegate.qml151
-rw-r--r--src/labs/stylekit/Switch.qml97
-rw-r--r--src/labs/stylekit/TextField.qml62
-rw-r--r--src/labs/stylekit/impl/BackgroundAndIndicatorDelegate.qml94
-rw-r--r--src/labs/stylekit/impl/BackgroundDelegate.qml12
-rw-r--r--src/labs/stylekit/impl/CMakeLists.txt32
-rw-r--r--src/labs/stylekit/impl/DelegateContainer.qml171
-rw-r--r--src/labs/stylekit/impl/DelegateSingleton.qml14
-rw-r--r--src/labs/stylekit/impl/FallbackStyle.qml226
-rw-r--r--src/labs/stylekit/impl/HandleDelegate.qml22
-rw-r--r--src/labs/stylekit/impl/IndicatorDelegate.qml76
-rw-r--r--src/labs/stylekit/impl/Shadow.qml28
-rw-r--r--src/labs/stylekit/impl/qqstylekitlayout.cpp538
-rw-r--r--src/labs/stylekit/impl/qqstylekitlayout_p.h168
-rw-r--r--src/labs/stylekit/qqstylekit.cpp131
-rw-r--r--src/labs/stylekit/qqstylekit_p.h86
-rw-r--r--src/labs/stylekit/qqstylekitcontrol.cpp59
-rw-r--r--src/labs/stylekit/qqstylekitcontrol_p.h55
-rw-r--r--src/labs/stylekit/qqstylekitcontrolproperties.cpp1014
-rw-r--r--src/labs/stylekit/qqstylekitcontrolproperties_p.h591
-rw-r--r--src/labs/stylekit/qqstylekitcontrols.cpp92
-rw-r--r--src/labs/stylekit/qqstylekitcontrols_p.h122
-rw-r--r--src/labs/stylekit/qqstylekitcontrolstate.cpp99
-rw-r--r--src/labs/stylekit/qqstylekitcontrolstate_p.h76
-rw-r--r--src/labs/stylekit/qqstylekitcustomcontrol.cpp32
-rw-r--r--src/labs/stylekit/qqstylekitcustomcontrol_p.h47
-rw-r--r--src/labs/stylekit/qqstylekitcustomtheme.cpp42
-rw-r--r--src/labs/stylekit/qqstylekitcustomtheme_p.h54
-rw-r--r--src/labs/stylekit/qqstylekitdebug.cpp361
-rw-r--r--src/labs/stylekit/qqstylekitdebug_p.h87
-rw-r--r--src/labs/stylekit/qqstylekitfont.cpp232
-rw-r--r--src/labs/stylekit/qqstylekitfont_p.h155
-rw-r--r--src/labs/stylekit/qqstylekitglobal.cpp10
-rw-r--r--src/labs/stylekit/qqstylekitglobal_p.h154
-rw-r--r--src/labs/stylekit/qqstylekitpalette.cpp42
-rw-r--r--src/labs/stylekit/qqstylekitpalette_p.h124
-rw-r--r--src/labs/stylekit/qqstylekitpropertyresolver.cpp674
-rw-r--r--src/labs/stylekit/qqstylekitpropertyresolver_p.h114
-rw-r--r--src/labs/stylekit/qqstylekitreader.cpp526
-rw-r--r--src/labs/stylekit/qqstylekitreader_p.h182
-rw-r--r--src/labs/stylekit/qqstylekitstorage.cpp21
-rw-r--r--src/labs/stylekit/qqstylekitstorage_p.h49
-rw-r--r--src/labs/stylekit/qqstylekitstyle.cpp304
-rw-r--r--src/labs/stylekit/qqstylekitstyle_p.h125
-rw-r--r--src/labs/stylekit/qqstylekittheme.cpp123
-rw-r--r--src/labs/stylekit/qqstylekittheme_p.h65
-rw-r--r--src/labs/stylekit/qqstylekitthemeproperties.cpp22
-rw-r--r--src/labs/stylekit/qqstylekitthemeproperties_p.h49
-rw-r--r--src/labs/stylekit/qqstylekitvariation.cpp15
-rw-r--r--src/labs/stylekit/qqstylekitvariation_p.h38
-rw-r--r--src/quickcontrols/configure.cmake7
67 files changed, 8810 insertions, 0 deletions
diff --git a/src/labs/CMakeLists.txt b/src/labs/CMakeLists.txt
index ae964701cf..4658c883f0 100644
--- a/src/labs/CMakeLists.txt
+++ b/src/labs/CMakeLists.txt
@@ -25,6 +25,10 @@ if(QT_FEATURE_quick_shadereffect AND TARGET Qt::Quick)
add_subdirectory(wavefrontmesh)
endif()
+if(QT_FEATURE_quickcontrols2_stylekit)
+ add_subdirectory(stylekit)
+endif()
+
if(QT_FEATURE_systemsemaphore AND TARGET Qt::Quick)
add_subdirectory(sharedimage)
endif()
diff --git a/src/labs/stylekit/Button.qml b/src/labs/stylekit/Button.qml
new file mode 100644
index 0000000000..ba36633142
--- /dev/null
+++ b/src/labs/stylekit/Button.qml
@@ -0,0 +1,56 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Button {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+ spacing: styleReader.spacing
+
+ icon.width: 24
+ icon.height: 24
+ icon.color: styleReader.text.color
+
+ StyleKitReader {
+ id: styleReader
+ type: control.flat ? StyleKitReader.FlatButton : StyleKitReader.Button
+ enabled: control.enabled
+ focused: control.activeFocus
+ checked: control.checked
+ hovered: control.hovered || control.pressed
+ pressed: control.pressed
+ palette: control.palette
+ }
+
+ contentItem: IconLabel {
+ spacing: control.spacing
+ mirrored: control.mirrored
+ display: control.display
+
+ icon: control.icon
+ text: control.text
+ font: control.font
+ color: styleReader.text.color
+ alignment: styleReader.text.alignment
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/CMakeLists.txt b/src/labs/stylekit/CMakeLists.txt
new file mode 100644
index 0000000000..b981bcac62
--- /dev/null
+++ b/src/labs/stylekit/CMakeLists.txt
@@ -0,0 +1,59 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## StyleKit Module:
+#####################################################################
+
+add_subdirectory(impl)
+
+qt_internal_add_qml_module(QtQuickStyleKit
+ URI Qt.labs.StyleKit
+ IMPORTS QtQuick.Controls.Basic
+ SOURCES
+ qqstylekit_p.h qqstylekit.cpp
+ qqstylekitcontrol_p.h qqstylekitcontrol.cpp
+ qqstylekitcontrols_p.h qqstylekitcontrols.cpp
+ qqstylekitcontrolstate_p.h qqstylekitcontrolstate.cpp
+ qqstylekitcontrolproperties_p.h qqstylekitcontrolproperties.cpp
+ qqstylekitcustomcontrol_p.h qqstylekitcustomcontrol.cpp
+ qqstylekitdebug_p.h qqstylekitdebug.cpp
+ qqstylekitglobal_p.h qqstylekitglobal.cpp
+ qqstylekitpalette_p.h qqstylekitpalette.cpp
+ qqstylekitfont_p.h qqstylekitfont.cpp
+ qqstylekitpropertyresolver_p.h qqstylekitpropertyresolver.cpp
+ qqstylekitreader_p.h qqstylekitreader.cpp
+ qqstylekitstorage_p.h qqstylekitstorage.cpp
+ qqstylekitstyle_p.h qqstylekitstyle.cpp
+ qqstylekittheme_p.h qqstylekittheme.cpp
+ qqstylekitthemeproperties_p.h qqstylekitthemeproperties.cpp
+ qqstylekitcustomtheme_p.h qqstylekitcustomtheme.cpp
+ qqstylekitvariation_p.h qqstylekitvariation.cpp
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QML_FILES
+ Button.qml
+ CheckBox.qml
+ ComboBox.qml
+ Frame.qml
+ ItemDelegate.qml
+ RadioButton.qml
+ RangeSlider.qml
+ Slider.qml
+ Style.qml
+ StyleKitAnimation.qml
+ StyleKitDelegate.qml
+ SpinBox.qml
+ Switch.qml
+ TextField.qml
+ Page.qml
+ Pane.qml
+ Popup.qml
+ LIBRARIES
+ Qt6::GuiPrivate
+ Qt6::Quick
+ Qt6::QuickPrivate
+ Qt6::QuickTemplates2
+ Qt6::QuickTemplates2Private
+)
diff --git a/src/labs/stylekit/CheckBox.qml b/src/labs/stylekit/CheckBox.qml
new file mode 100644
index 0000000000..b8750a40f7
--- /dev/null
+++ b/src/labs/stylekit/CheckBox.qml
@@ -0,0 +1,87 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import QtQuick.Controls.impl
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.CheckBox {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ checkBoxLayout.implicitWidth + implicitContentWidth)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + styleReader.topPadding + styleReader.bottomPadding,
+ checkBoxLayout.implicitHeight)
+
+ leftPadding: checkBoxLayout.padding.left
+ topPadding: checkBoxLayout.padding.top
+ rightPadding: checkBoxLayout.padding.right
+ bottomPadding: checkBoxLayout.padding.bottom
+
+ spacing: styleReader.spacing
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.CheckBox
+ enabled: control.enabled
+ focused: control.activeFocus
+ checked: control.checked
+ hovered: control.hovered
+ pressed: control.pressed
+ palette: control.palette
+ }
+
+ StyleKitLayout {
+ id: checkBoxLayout
+ container: control
+ contentMargins {
+ left: styleReader.leftPadding
+ right: styleReader.rightPadding
+ top: styleReader.topPadding
+ bottom: styleReader.bottomPadding
+ }
+ layoutItems: [
+ // We don't lay out the contentItem here because it occupies the remaining space
+ // as calculated by control internal logic.
+ StyleKitLayoutItem {
+ id: indicatorItem
+ item: control.indicator
+ alignment: styleReader.indicator.alignment
+ margins.left: styleReader.indicator.leftMargin
+ margins.right: styleReader.indicator.rightMargin
+ margins.top: styleReader.indicator.topMargin
+ margins.bottom: styleReader.indicator.bottomMargin
+ fillWidth: styleReader.indicator.implicitWidth === Style.Stretch
+ fillHeight: styleReader.indicator.implicitHeight === Style.Stretch
+ }
+ ]
+ spacing: styleReader.spacing
+ mirrored: control.mirrored
+ }
+
+ indicator: IndicatorDelegate {
+ parentControl: control
+ indicatorProperties: styleReader.indicator
+ x: indicatorItem.x
+ y: indicatorItem.y
+ width: indicatorItem.width
+ height: indicatorItem.height
+ }
+
+ contentItem: CheckLabel {
+ text: control.text
+ font: control.font
+ color: styleReader.text.color
+ horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
+ verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/ComboBox.qml b/src/labs/stylekit/ComboBox.qml
new file mode 100644
index 0000000000..d51c8906ef
--- /dev/null
+++ b/src/labs/stylekit/ComboBox.qml
@@ -0,0 +1,135 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.ComboBox {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ comboLayout.implicitWidth + implicitContentWidth)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + styleReader.topPadding + styleReader.bottomPadding,
+ comboLayout.implicitHeight)
+
+ leftPadding: comboLayout.padding.left
+ rightPadding: comboLayout.padding.right
+ topPadding: comboLayout.padding.top
+ bottomPadding: comboLayout.padding.bottom
+
+ spacing: styleReader.spacing
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.ComboBox
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.hovered || control.pressed
+ pressed: control.down
+ highlighted: control.highlighted
+ // variations: editable, flat
+ palette: control.palette
+ }
+
+ StyleKitLayout {
+ id: comboLayout
+ container: control
+ contentMargins {
+ left: styleReader.leftPadding
+ right: styleReader.rightPadding
+ top: styleReader.topPadding
+ bottom: styleReader.bottomPadding
+ }
+ layoutItems: [
+ // We don't lay out the contentItem here because it occupies the remaining space
+ // as calculated by control internal logic.
+ StyleKitLayoutItem {
+ id: indicatorItem
+ item: control.indicator
+ alignment: Qt.AlignRight | Qt.AlignVCenter
+ margins.left: styleReader.indicator.leftMargin
+ margins.right: styleReader.indicator.rightMargin
+ margins.top: styleReader.indicator.topMargin
+ margins.bottom: styleReader.indicator.bottomMargin
+ fillWidth: styleReader.indicator.implicitWidth === Style.Stretch
+ fillHeight: styleReader.indicator.implicitHeight === Style.Stretch
+ }
+ ]
+ spacing: styleReader.spacing
+ mirrored: control.mirrored
+ }
+
+ // TODO: Use the ItemDelegate control as is for now,
+ // later we might want to customize it seperately for combobox using control "variations"
+ delegate: ItemDelegate {
+ required property var model
+ required property int index
+
+ width: ListView.view.width
+ text: model[control.textRole]
+ palette.text: control.palette.text
+ palette.highlightedText: control.palette.highlightedText
+ highlighted: control.highlightedIndex === index
+ hoverEnabled: control.hoverEnabled
+ }
+
+ indicator: IndicatorDelegate {
+ parentControl: control
+ indicatorProperties: styleReader.indicator
+ x: indicatorItem.x
+ y: indicatorItem.y
+ width: indicatorItem.width
+ height: indicatorItem.height
+ }
+
+ contentItem: T.TextField {
+ text: control.editable ? control.editText : control.displayText
+ enabled: control.editable
+ autoScroll: control.editable
+ readOnly: control.down
+ inputMethodHints: control.inputMethodHints
+ validator: control.validator
+ selectByMouse: control.selectTextByMouse
+
+ color: styleReader.text.color
+ verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
+ horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
+
+ selectionColor: control.palette.highlight
+ selectedTextColor: control.palette.highlightedText
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+
+ // TODO: Use the Popup control as is for now,
+ // later we might want to customize it seperately for combobox using control "variations"
+ popup: Popup {
+ y: control.height
+ width: control.width
+ height: Math.min(contentItem.implicitHeight, control.Window.height - topMargin - bottomMargin)
+
+ palette.text: control.palette.text
+ palette.highlight: control.palette.highlight
+ palette.highlightedText: control.palette.highlightedText
+ palette.windowText: control.palette.windowText
+ palette.buttonText: control.palette.buttonText
+
+ contentItem: ListView {
+ clip: true
+ implicitHeight: contentHeight
+ model: control.delegateModel
+ currentIndex: control.highlightedIndex
+ highlightMoveDuration: 0
+
+ T.ScrollIndicator.vertical: ScrollIndicator { }
+ }
+ }
+}
diff --git a/src/labs/stylekit/Frame.qml b/src/labs/stylekit/Frame.qml
new file mode 100644
index 0000000000..9d09b258f9
--- /dev/null
+++ b/src/labs/stylekit/Frame.qml
@@ -0,0 +1,35 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Frame {
+ id: control
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.Frame
+ enabled: control.enabled
+ focused: control.activeFocus
+ palette: control.palette
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/ItemDelegate.qml b/src/labs/stylekit/ItemDelegate.qml
new file mode 100644
index 0000000000..6542eb239b
--- /dev/null
+++ b/src/labs/stylekit/ItemDelegate.qml
@@ -0,0 +1,57 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.ItemDelegate {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding,
+ implicitIndicatorHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+ spacing: styleReader.spacing
+
+ icon.width: 16
+ icon.height: 16
+ icon.color: styleReader.text.color
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.ItemDelegate
+ enabled: control.enabled
+ focused: control.activeFocus
+ checked: control.checked
+ hovered: control.hovered
+ pressed: control.pressed
+ highlighted: control.highlighted
+ palette: control.palette
+ }
+
+ contentItem: IconLabel {
+ spacing: control.spacing
+ mirrored: control.mirrored
+ display: control.display
+ icon: control.icon
+ text: control.text
+ font: control.font
+ color: styleReader.text.color
+ alignment: styleReader.text.alignment
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/Page.qml b/src/labs/stylekit/Page.qml
new file mode 100644
index 0000000000..cf2d58cc80
--- /dev/null
+++ b/src/labs/stylekit/Page.qml
@@ -0,0 +1,39 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Page {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding,
+ implicitHeaderWidth, implicitFooterWidth)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding
+ + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.Page
+ enabled: control.enabled
+ focused: control.activeFocus
+ palette: control.palette
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/Pane.qml b/src/labs/stylekit/Pane.qml
new file mode 100644
index 0000000000..615e6db7f2
--- /dev/null
+++ b/src/labs/stylekit/Pane.qml
@@ -0,0 +1,36 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Pane {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.Pane
+ enabled: control.enabled
+ focused: control.activeFocus
+ palette: control.palette
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/Popup.qml b/src/labs/stylekit/Popup.qml
new file mode 100644
index 0000000000..adfcc74f08
--- /dev/null
+++ b/src/labs/stylekit/Popup.qml
@@ -0,0 +1,38 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Popup {
+ id: control
+
+ readonly property StyleKitReader __styleReader: StyleKitReader {
+ // TODO: making StyleKitReader a child object of T.Popup makes the
+ // popup not open on press. So use a __styleReader property for now
+ // until we know the reason why.
+ type: StyleKitReader.Popup
+ enabled: control.enabled
+ focused: control.activeFocus
+ palette: control.palette
+ }
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: __styleReader.leftPadding
+ topPadding: __styleReader.topPadding
+ rightPadding: __styleReader.rightPadding
+ bottomPadding: __styleReader.bottomPadding
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: control.__styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/RadioButton.qml b/src/labs/stylekit/RadioButton.qml
new file mode 100644
index 0000000000..77c03ccd56
--- /dev/null
+++ b/src/labs/stylekit/RadioButton.qml
@@ -0,0 +1,87 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.RadioButton {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ radioButtonLayout.implicitWidth + implicitContentWidth)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + styleReader.topPadding + styleReader.bottomPadding,
+ radioButtonLayout.implicitHeight)
+
+ leftPadding: radioButtonLayout.padding.left
+ topPadding: radioButtonLayout.padding.top
+ rightPadding: radioButtonLayout.padding.right
+ bottomPadding: radioButtonLayout.padding.bottom
+
+ spacing: styleReader.spacing
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.RadioButton
+ enabled: control.enabled
+ focused: control.activeFocus
+ checked: control.checked
+ hovered: control.hovered
+ pressed: control.pressed
+ palette: control.palette
+ }
+
+ StyleKitLayout {
+ id: radioButtonLayout
+ container: control
+ contentMargins {
+ left: styleReader.leftPadding
+ right: styleReader.rightPadding
+ top: styleReader.topPadding
+ bottom: styleReader.bottomPadding
+ }
+ layoutItems: [
+ // We don't lay out the contentItem here because it occupies the remaining space
+ // as calculated by control internal logic.
+ StyleKitLayoutItem {
+ id: indicatorItem
+ item: control.indicator
+ alignment: styleReader.indicator.alignment
+ margins.left: styleReader.indicator.leftMargin
+ margins.right: styleReader.indicator.rightMargin
+ margins.top: styleReader.indicator.topMargin
+ margins.bottom: styleReader.indicator.bottomMargin
+ fillWidth: styleReader.indicator.implicitWidth === Style.Stretch
+ fillHeight: styleReader.indicator.implicitHeight === Style.Stretch
+ }
+ ]
+ spacing: styleReader.spacing
+ mirrored: control.mirrored
+ }
+
+ indicator: IndicatorDelegate {
+ parentControl: control
+ indicatorProperties: styleReader.indicator
+ x: indicatorItem.x
+ y: indicatorItem.y
+ width: indicatorItem.width
+ height: indicatorItem.height
+ }
+
+ contentItem: CheckLabel {
+ text: control.text
+ font: control.font
+ color: styleReader.text.color
+ horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
+ verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/RangeSlider.qml b/src/labs/stylekit/RangeSlider.qml
new file mode 100644
index 0000000000..2467f23450
--- /dev/null
+++ b/src/labs/stylekit/RangeSlider.qml
@@ -0,0 +1,135 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.RangeSlider {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ first.implicitHandleWidth + leftPadding + rightPadding,
+ second.implicitHandleWidth + leftPadding + rightPadding,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ first.implicitHandleHeight + topPadding + bottomPadding,
+ second.implicitHandleHeight + topPadding + bottomPadding,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReaderFirst.leftPadding
+ topPadding: styleReaderFirst.topPadding
+ rightPadding: styleReaderFirst.rightPadding
+ bottomPadding: styleReaderFirst.bottomPadding
+ spacing: styleReaderFirst.spacing
+
+ states: [
+ /* The delegate logic is moved out of the delegate, to relieve the style
+ * developer from having to re-invent it if he changes the delegate. To
+ * disable it, 'states' can be set to an empty array from the outside. */
+ State {
+ when: horizontal
+ PropertyChanges {
+ // The width of the handle is fixed. But its margins are used
+ // to describe the area within the content area that the handle
+ // is allowed to move.
+ control.first.handle.x: leftPadding + styleReaderFirst.handle.leftMargin
+ + (first.visualPosition * (availableWidth - styleReaderFirst.handle.leftMargin
+ - styleReaderFirst.handle.rightMargin - first.handle.width))
+ // The height of the handle is fixed. But the margins are used to
+ // shift its position up or down from the center of the content area.
+ control.first.handle.y: topPadding
+ + styleReaderFirst.handle.topMargin - styleReaderFirst.handle.bottomMargin
+ + (availableHeight - first.handle.height) / 2
+
+ control.second.handle.x: leftPadding + styleReaderSecond.handle.leftMargin
+ + (second.visualPosition * (availableWidth - styleReaderSecond.handle.leftMargin
+ - styleReaderSecond.handle.rightMargin - second.handle.width))
+
+ control.second.handle.y: topPadding
+ + styleReaderSecond.handle.topMargin - styleReaderSecond.handle.bottomMargin
+ + (availableHeight - second.handle.height) / 2
+ }
+ },
+ State {
+ // Note: we deliberatly flip margins (but not padding) in vertical
+ // mode since a vertical slider is logically a flipped horizontal slider.
+ when: control.vertical
+ PropertyChanges {
+ control.first.handle.x: leftPadding
+ + styleReaderFirst.handle.topMargin - styleReaderFirst.handle.bottomMargin
+ + (availableWidth + first.handle.height) / 2
+ control.first.handle.y: topPadding + styleReaderFirst.handle.leftMargin
+ + (first.visualPosition * (availableHeight - styleReaderFirst.handle.leftMargin
+ - styleReaderFirst.handle.rightMargin - first.handle.width))
+ control.first.handle.rotation: 90
+ control.first.handle.transformOrigin: Item.TopLeft
+
+ control.second.handle.x: leftPadding
+ + styleReaderSecond.handle.topMargin - styleReaderSecond.handle.bottomMargin
+ + (availableWidth + second.handle.height) / 2
+ control.second.handle.y: topPadding + styleReaderSecond.handle.leftMargin
+ + (second.visualPosition * (availableHeight - styleReaderSecond.handle.leftMargin
+ - styleReaderSecond.handle.rightMargin - second.handle.width))
+ control.second.handle.rotation: 90
+ control.second.handle.transformOrigin: Item.TopLeft
+ }
+ }
+ ]
+
+ readonly property StyleKitReader styleReader: styleReaderFirst
+
+ StyleKitReader {
+ id: styleReaderFirst
+ type: StyleKitReader.Slider
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.first.hovered
+ pressed: control.first.pressed
+ palette: control.palette
+ vertical: !control.horizontal
+ }
+
+ StyleKitReader {
+ id: styleReaderSecond
+ type: StyleKitReader.Slider
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.second.hovered
+ pressed: control.second.pressed
+ palette: control.palette
+ vertical: !control.horizontal
+ }
+
+ StyleKitReader {
+ id: styleReaderIndicator
+ type: StyleKitReader.Slider
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.hovered || control.first.hovered || control.second.hovered
+ pressed: control.first.pressed || control.second.pressed
+ palette: control.palette
+ vertical: !control.horizontal
+ }
+
+ first.handle: HandleDelegate {
+ parentControl: control
+ handleProperties: styleReaderFirst.handle.first
+ }
+
+ second.handle: HandleDelegate {
+ parentControl: control
+ handleProperties: styleReaderSecond.handle.second
+ }
+
+ background: BackgroundAndIndicatorDelegate {
+ parentControl: control
+ indicatorProperties: styleReaderIndicator.indicator
+ backgroundProperties: styleReaderIndicator.background
+ indicator.firstProgress: control.first.position
+ indicator.secondProgress: control.second.position
+ vertical: control.vertical
+ }
+}
diff --git a/src/labs/stylekit/Slider.qml b/src/labs/stylekit/Slider.qml
new file mode 100644
index 0000000000..2b4ca71c96
--- /dev/null
+++ b/src/labs/stylekit/Slider.qml
@@ -0,0 +1,87 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Slider {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitHandleWidth + leftPadding + rightPadding,
+ implicitContentWidth + leftPadding + rightPadding)
+
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitHandleHeight + topPadding + bottomPadding,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+ spacing: styleReader.spacing
+
+ states: [
+ /* The delegate logic is moved out of the delegate, to relieve the style
+ * developer from having to re-invent it if he changes the delegate. To
+ * disable it, 'states' can be set to an empty array from the outside. */
+ State {
+ when: horizontal
+ PropertyChanges {
+ // The width of the handle is fixed. But its margins are is used
+ // to describe the area within the content area that the handle
+ // is allowed to move.
+ control.handle.x: leftPadding + styleReader.handle.leftMargin
+ + (visualPosition * (availableWidth - styleReader.handle.leftMargin
+ - styleReader.handle.rightMargin - handle.width))
+ // The height of the handle is fixed. But the margins are used to
+ // shift its position up or down from the center of the content area.
+ control.handle.y: topPadding
+ + styleReader.handle.topMargin - styleReader.handle.bottomMargin
+ + (availableHeight - control.handle.height) / 2
+ }
+ },
+ State {
+ // Note: we deliberatly flip margins (but not padding) in vertical
+ // mode since a vertical slider is logically a flipped horizontal slider.
+ when: control.vertical
+ PropertyChanges {
+ control.handle.x: leftPadding
+ + styleReader.handle.topMargin - styleReader.handle.bottomMargin
+ + (availableWidth + handle.height) / 2
+ control.handle.y: topPadding + styleReader.handle.leftMargin
+ + (visualPosition * (availableHeight - styleReader.handle.leftMargin
+ - styleReader.handle.rightMargin - handle.width))
+ control.handle.rotation: 90
+ control.handle.transformOrigin: Item.TopLeft
+ }
+ }
+ ]
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.Slider
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.hovered
+ pressed: control.pressed
+ palette: control.palette
+ vertical: !control.horizontal
+ }
+
+ handle: HandleDelegate {
+ parentControl: control
+ handleProperties: styleReader.handle
+ }
+
+ background: BackgroundAndIndicatorDelegate {
+ parentControl: control
+ indicatorProperties: styleReader.indicator
+ backgroundProperties: styleReader.background
+ indicator.secondProgress: control.position
+ vertical: control.vertical
+ }
+}
diff --git a/src/labs/stylekit/SpinBox.qml b/src/labs/stylekit/SpinBox.qml
new file mode 100644
index 0000000000..ac2ea61e2c
--- /dev/null
+++ b/src/labs/stylekit/SpinBox.qml
@@ -0,0 +1,140 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.SpinBox {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: spinBoxLayout.padding.left
+ topPadding: spinBoxLayout.padding.top
+ rightPadding: spinBoxLayout.padding.right
+ bottomPadding: spinBoxLayout.padding.bottom
+ spacing: styleReader.spacing
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.SpinBox
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.hovered || control.down.hovered || control.up.hovered
+ pressed: control.down.pressed || control.up.pressed
+ palette: control.palette
+ }
+
+ StyleKitReader {
+ id: upProperties
+ type: StyleKitReader.SpinBox
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.up.hovered
+ pressed: control.up.pressed
+ palette: control.palette
+ }
+
+ StyleKitReader {
+ id: downProperties
+ type: StyleKitReader.SpinBox
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.down.hovered
+ pressed: control.down.pressed
+ palette: control.palette
+ }
+
+ StyleKitLayout {
+ id: spinBoxLayout
+ container: control
+ contentMargins {
+ // Copy the other styles, and add indicator width to padding
+ left: styleReader.leftPadding
+ right: styleReader.rightPadding
+ top: styleReader.topPadding
+ bottom: styleReader.bottomPadding
+ }
+ layoutItems: [
+ // We don't lay out the contentItem here because it occupies the remaining space
+ // as calculated by control internal logic.
+ StyleKitLayoutItem {
+ id: upIndicatorItem
+ item: control.up.indicator
+ alignment: styleReader.indicator.up.alignment
+ margins.left: styleReader.indicator.up.leftMargin
+ margins.right: styleReader.indicator.up.rightMargin
+ margins.top: styleReader.indicator.up.topMargin
+ margins.bottom: styleReader.indicator.up.bottomMargin
+ fillWidth: styleReader.indicator.up.implicitWidth === Style.Stretch
+ fillHeight: styleReader.indicator.up.implicitHeight === Style.Stretch
+ },
+ StyleKitLayoutItem {
+ id: downIndicatorItem
+ item: control.down.indicator
+ alignment: styleReader.indicator.down.alignment
+ margins.left: styleReader.indicator.down.leftMargin
+ margins.right: styleReader.indicator.down.rightMargin
+ margins.top: styleReader.indicator.down.topMargin
+ margins.bottom: styleReader.indicator.down.bottomMargin
+ fillWidth: styleReader.indicator.down.implicitWidth === Style.Stretch
+ fillHeight: styleReader.indicator.down.implicitHeight === Style.Stretch
+ }
+ ]
+ spacing: styleReader.spacing
+ mirrored: control.mirrored
+ }
+
+ validator: IntValidator {
+ locale: control.locale.name
+ bottom: Math.min(control.from, control.to)
+ top: Math.max(control.from, control.to)
+ }
+
+ contentItem: TextInput {
+ z: 2
+ text: control.displayText
+
+ font: control.font
+ selectionColor: control.palette.highlight
+ selectedTextColor: control.palette.highlightedText
+ color: styleReader.text.color
+ horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
+ verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
+
+ readOnly: !control.editable
+ validator: control.validator
+ inputMethodHints: control.inputMethodHints
+ clip: width < implicitWidth
+ }
+
+ up.indicator: IndicatorDelegate {
+ parentControl: control
+ indicatorProperties: upProperties.indicator.up
+ x: upIndicatorItem.x
+ y: upIndicatorItem.y
+ width: upIndicatorItem.width
+ height: upIndicatorItem.height
+ }
+
+ down.indicator: IndicatorDelegate {
+ parentControl: control
+ indicatorProperties: downProperties.indicator.down
+ x: downIndicatorItem.x
+ y: downIndicatorItem.y
+ width: downIndicatorItem.width
+ height: downIndicatorItem.height
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/Style.qml b/src/labs/stylekit/Style.qml
new file mode 100644
index 0000000000..20163c250b
--- /dev/null
+++ b/src/labs/stylekit/Style.qml
@@ -0,0 +1,23 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+BaseStyle {
+ id: style
+
+ fallbackStyle: FallbackStyle {}
+
+ /* This function is a work-around (called from c++) since we haven't found a way to
+ * instantiate a Theme inside the context of a Style from c++ otherwise. Doing so is
+ * needed in order to allow custom style properties to be added as children of a
+ * Style, and at the same time, be able to access them from within a Theme instance.
+ * For this to work, a style with custom properties also needs to set
+ * 'pragma ComponentBehavior: Bound'. */
+ function createThemeInsideStyle(themeComponent) {
+ return themeComponent.createObject(style)
+ }
+}
diff --git a/src/labs/stylekit/StyleKitAnimation.qml b/src/labs/stylekit/StyleKitAnimation.qml
new file mode 100644
index 0000000000..e7947eb48a
--- /dev/null
+++ b/src/labs/stylekit/StyleKitAnimation.qml
@@ -0,0 +1,122 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+
+ParallelAnimation {
+ id: root
+ property alias easing: colorAnimation.easing
+ property alias duration: colorAnimation.duration
+
+ property bool animateColors: false
+ property bool animateControlGeometry: false
+
+ property bool animateBackgroundColors: false
+ property bool animateBackgroundGeometry: false
+ property bool animateBackgroundRadii: false
+ property bool animateBackgroundBorder: false
+ property bool animateBackgroundShadow: false
+ property bool animateBackgroundScaleAndRotation: false
+
+ property bool animateHandleColors: false
+ property bool animateHandleGeometry: false
+ property bool animateHandleRadii: false
+ property bool animateHandleBorder: false
+ property bool animateHandleShadow: false
+ property bool animateHandleScaleAndRotation: false
+
+ property bool animateIndicatorColors: false
+ property bool animateIndicatorGeometry: false
+ property bool animateIndicatorBorder: false
+ property bool animateIndicatorRadii: false
+ property bool animateIndicatorShadow: false
+ property bool animateIndicatorScaleAndRotation: false
+
+ readonly property string __geometryProps:
+ "$.implicitWidth, $.implicitHeight, "
+ + "$.margins, $.leftMargin, $.rightMargin, $.topMargin, $.bottomMargin, "
+ + "$.leftPadding, $.rightPadding, $.topPadding, $.bottomPadding, "
+ + "$.minimumWidth, "
+ readonly property string __colorProps:
+ "$.color, $.border.color, $.image.color, $.shadow.color, "
+ readonly property string __radiiProps:
+ "$.radius, $.topLeftRadius, $.topRightRadius, $.bottomLeftRadius, $.bottomRightRadius, "
+ readonly property string __scaleAndRotationProps: "$.scale, $.rotation, "
+ readonly property string __borderProps: "$.border.width, "
+ readonly property string __shadowProps:
+ "$.shadow.verticalOffset, $.shadow.horizontalOffset, "
+ + "$.shadow.scale, $.shadow.blur, "
+
+
+ function __animateIndicators(doAnimate, anim, props) {
+ if (!doAnimate)
+ return
+ anim.properties += props.replace(/\$/g, "indicator")
+ anim.properties += props.replace(/\$/g, "indicator.foreground")
+ anim.properties += props.replace(/\$/g, "indicator.up")
+ anim.properties += props.replace(/\$/g, "indicator.up.foreground")
+ anim.properties += props.replace(/\$/g, "indicator.down")
+ anim.properties += props.replace(/\$/g, "indicator.down.foreground")
+ }
+
+ function __animateHandles(doAnimate, anim, props) {
+ if (!doAnimate)
+ return
+ anim.properties += props.replace(/\$/g, "handle")
+ anim.properties += props.replace(/\$/g, "handle.first")
+ anim.properties += props.replace(/\$/g, "handle.second")
+ }
+
+ Component.onCompleted: {
+ if (animateControlGeometry)
+ numberAnimation.properties += "spacing, padding, leftPadding, rightPadding, topPadding, bottomPadding"
+
+ if (animateBackgroundGeometry)
+ numberAnimation.properties += __geometryProps.replace(/\$/g, "background")
+ if (animateBackgroundRadii)
+ numberAnimation.properties += __radiiProps.replace(/\$/g, "background")
+ if (animateBackgroundBorder)
+ numberAnimation.properties += __borderProps.replace(/\$/g, "background")
+ if (animateBackgroundShadow)
+ numberAnimation.properties += __shadowProps.replace(/\$/g, "background")
+ if (animateBackgroundScaleAndRotation)
+ numberAnimation.properties += __scaleAndRotationProps.replace(/\$/g, "background")
+
+ __animateIndicators(animateIndicatorGeometry, numberAnimation, __geometryProps)
+ __animateIndicators(animateIndicatorRadii, numberAnimation, __radiiProps)
+ __animateIndicators(animateIndicatorBorder, numberAnimation, __borderProps)
+ __animateIndicators(animateIndicatorShadow, numberAnimation, __shadowProps)
+ __animateIndicators(animateIndicatorScaleAndRotation, numberAnimation, __scaleAndRotationProps)
+
+ __animateHandles(animateHandleGeometry, numberAnimation, __geometryProps)
+ __animateHandles(animateHandleRadii, numberAnimation, __radiiProps)
+ __animateHandles(animateHandleBorder, numberAnimation, __borderProps)
+ __animateHandles(animateHandleShadow, numberAnimation, __shadowProps)
+ __animateHandles(animateHandleScaleAndRotation, numberAnimation, __scaleAndRotationProps)
+
+ if (!animateColors) {
+ /* Note: a ColorAnimation will animate all colors by default if 'properties'
+ * is empty. So we take advantage of that, and only add the per-delegate properties
+ * when not all colors should animate. For the same reason, we need to set properties
+ * to something else than "" if no colors should animate. */
+ if (animateBackgroundColors)
+ colorAnimation.properties += __colorProps.replace(/\$/g, "background")
+ __animateIndicators(animateIndicatorColors, colorAnimation, __colorProps)
+ __animateHandles(animateHandleColors, colorAnimation, __colorProps)
+
+ if (colorAnimation.properties === "")
+ colorAnimation.properties = "_none_"
+ }
+ }
+
+ ColorAnimation {
+ id: colorAnimation
+ }
+
+ NumberAnimation {
+ id: numberAnimation
+ easing: root.easing
+ duration: root.duration
+ }
+}
diff --git a/src/labs/stylekit/StyleKitDelegate.qml b/src/labs/stylekit/StyleKitDelegate.qml
new file mode 100644
index 0000000000..d1f741e674
--- /dev/null
+++ b/src/labs/stylekit/StyleKitDelegate.qml
@@ -0,0 +1,151 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Effects
+import QtQuick.Controls.impl
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+/* This delegate takes care of loading the individual elements that are needed
+ in order to draw it according to its configuration in the style.
+ Since some delegates have a gradient, others an image, and some perhaps
+ both, we use dynamic loading to reduce memory usage. We could use Loaders
+ for this, but from testing, using JS appears visually more performant.
+ TODO: move most of this code to c++? Collapse all elements into a single
+ scene-graph node? Redraw everything from a single update, rather than using
+ property bindings?
+*/
+Item {
+ id: root
+
+ // If an implicit size has been set on the delegate in the style, it
+ // wins. Otherwise we use the implicit size of the image
+ implicitWidth: __imageInstance ? __imageInstance.implicitWidth : 0
+ implicitHeight: __imageInstance ? __imageInstance.implicitHeight : 0
+ width: parent.width
+ height: parent.height
+ opacity: delegateProperties.opacity
+
+ required property QtObject control
+ required property StyleKitDelegateProperties delegateProperties
+
+ property Item __colorInstance: null
+ property Item __gradientInstance: null
+ property Item __imageInstance: null
+
+ onDelegatePropertiesChanged: {
+ instantiateColorDelegate()
+ instantiateGradientDelegate()
+ instantiateImageDelegate()
+ }
+
+ Connections {
+ target: delegateProperties
+ function onColorChanged() { instantiateColorDelegate() }
+ function onGradientChanged() { instantiateGradientDelegate() }
+ }
+
+ Connections {
+ target: delegateProperties.image
+ function onSourceChanged() { instantiateImageDelegate() }
+ function onColorChanged() { instantiateImageDelegate() }
+ }
+
+ Component {
+ id: colorDelegate
+ Rectangle {
+ required property StyleKitDelegateProperties delegateProperties
+ width: parent.width
+ height: parent.height
+ topLeftRadius: delegateProperties.topLeftRadius
+ topRightRadius: delegateProperties.topRightRadius
+ bottomLeftRadius: delegateProperties.bottomLeftRadius
+ bottomRightRadius: delegateProperties.bottomRightRadius
+ color: delegateProperties.color
+ border.width: delegateProperties.border.width
+ border.color: delegateProperties.border.color
+ }
+ }
+
+ Component {
+ id: gradientDelegate
+ Rectangle {
+ required property StyleKitDelegateProperties delegateProperties
+ width: parent.width
+ height: parent.height
+ topLeftRadius: delegateProperties.topLeftRadius
+ topRightRadius: delegateProperties.topRightRadius
+ bottomLeftRadius: delegateProperties.bottomLeftRadius
+ bottomRightRadius: delegateProperties.bottomRightRadius
+ border.width: delegateProperties.border.width
+ border.color: delegateProperties.border.color
+ gradient: delegateProperties.gradient
+ visible: delegateProperties.gradient !== null
+ }
+ }
+
+ Component {
+ id: imageDelegate
+ ColorImage {
+ required property StyleKitDelegateProperties delegateProperties
+ width: parent.width
+ height: parent.height
+ color: delegateProperties.image.color
+ source: delegateProperties.image.source
+ fillMode: delegateProperties.image.fillMode
+ visible: status === Image.Ready
+ }
+ }
+
+ function instantiateColorDelegate() {
+ if (__colorInstance)
+ return
+
+ // Delay instantiating element until needed
+ if (delegateProperties.color.a === 0 &&
+ (delegateProperties.border.color.a === 0
+ || delegateProperties.border.width === 0))
+ return
+
+ /* Note: we adjust z to ensure the elements are stacked correct, no matter the
+ * order in which they are instantiated. And we use negative z values to ensure
+ * that any children (with a default z === 0) of a "subclass" of the StyleKitDelegate
+ * ends up on top of this again. */
+ let prevInstance = __colorInstance
+ __colorInstance = colorDelegate.createObject(root, { z: -3, delegateProperties: root.delegateProperties })
+ if (prevInstance)
+ prevInstance.destroy()
+ }
+
+
+ function instantiateGradientDelegate() {
+ if (__gradientInstance)
+ return
+
+ // Delay instantiating element until needed
+ if (delegateProperties.gradient === null)
+ return
+
+ let prevInstance = __gradientInstance
+ __gradientInstance = gradientDelegate.createObject(root, { z: -2, delegateProperties: root.delegateProperties })
+ if (prevInstance)
+ prevInstance.destroy()
+ }
+
+ function instantiateImageDelegate() {
+ if (__imageInstance)
+ return
+
+ // Delay instantiating element until needed
+ if (delegateProperties.image.source === "" || delegateProperties.image.color.a === 0)
+ return
+
+ let prevInstance = __imageInstance
+ __imageInstance = imageDelegate.createObject(root, { z: -1, delegateProperties: root.delegateProperties })
+ if (prevInstance)
+ prevInstance.destroy()
+ }
+
+}
diff --git a/src/labs/stylekit/Switch.qml b/src/labs/stylekit/Switch.qml
new file mode 100644
index 0000000000..c8e1a6237d
--- /dev/null
+++ b/src/labs/stylekit/Switch.qml
@@ -0,0 +1,97 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import QtQuick.Controls.impl
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.Switch {
+ id: control
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding,
+ implicitIndicatorHeight + topPadding + bottomPadding)
+
+ leftPadding: switchLayout.padding.left
+ topPadding: switchLayout.padding.top
+ rightPadding: switchLayout.padding.right
+ bottomPadding: switchLayout.padding.bottom
+ spacing: styleReader.spacing
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.SwitchControl
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.hovered
+ pressed: control.pressed
+ checked: control.checked
+ palette: control.palette
+ }
+
+ StyleKitLayout {
+ id: switchLayout
+ container: control
+ contentMargins {
+ left: styleReader.leftPadding
+ right: styleReader.rightPadding
+ top: styleReader.topPadding
+ bottom: styleReader.bottomPadding
+ }
+ layoutItems: [
+ // We don't lay out the contentItem here because it occupies the remaining space
+ // as calculated by control internal logic.
+ StyleKitLayoutItem {
+ id: indicatorItem
+ item: control.indicator
+ alignment: styleReader.indicator.alignment
+ margins.left: styleReader.indicator.leftMargin
+ margins.right: styleReader.indicator.rightMargin
+ margins.top: styleReader.indicator.topMargin
+ margins.bottom: styleReader.indicator.bottomMargin
+ fillWidth: styleReader.indicator.implicitWidth === Style.Stretch
+ fillHeight: styleReader.indicator.implicitHeight === Style.Stretch
+ }
+ ]
+ spacing: styleReader.spacing
+ mirrored: control.mirrored
+ }
+
+ indicator: IndicatorDelegate {
+ parentControl: control
+ indicatorProperties: styleReader.indicator
+ x: indicatorItem.x
+ y: indicatorItem.y
+ width: indicatorItem.width
+ height: indicatorItem.height
+
+ HandleDelegate {
+ parentControl: control
+ handleProperties: styleReader.handle
+ x: control.checked
+ ? indicator.width - width - styleReader.handle.rightMargin
+ : styleReader.handle.leftMargin
+ y: styleReader.handle.topMargin - styleReader.handle.bottomMargin
+ + (indicator.height - height) / 2
+ z: 1
+ Behavior on x { NumberAnimation { duration: 50 } } // factor animation out to a style property!
+ }
+ }
+
+ contentItem: CheckLabel {
+ text: control.text
+ font: control.font
+ color: styleReader.text.color
+ horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
+ verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/TextField.qml b/src/labs/stylekit/TextField.qml
new file mode 100644
index 0000000000..55d75498be
--- /dev/null
+++ b/src/labs/stylekit/TextField.qml
@@ -0,0 +1,62 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+T.TextField {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ contentHeight + topPadding + bottomPadding,
+ placeholder.implicitHeight + topPadding + bottomPadding)
+
+ leftPadding: styleReader.leftPadding
+ topPadding: styleReader.topPadding
+ rightPadding: styleReader.rightPadding
+ bottomPadding: styleReader.bottomPadding
+
+ color: styleReader.text.color
+ verticalAlignment: styleReader.text.alignment & Qt.AlignVertical_Mask
+ horizontalAlignment: styleReader.text.alignment & Qt.AlignHorizontal_Mask
+
+ selectionColor: control.palette.highlight
+ selectedTextColor: control.palette.highlightedText
+ placeholderTextColor: control.palette.placeholderText
+
+ StyleKitReader {
+ id: styleReader
+ type: StyleKitReader.TextField
+ enabled: control.enabled
+ focused: control.activeFocus
+ hovered: control.hovered
+ palette: control.palette
+ }
+
+ PlaceholderText {
+ id: placeholder
+ x: control.leftPadding
+ y: control.topPadding
+ width: control.width - (control.leftPadding + control.rightPadding)
+ height: control.height - (control.topPadding + control.bottomPadding)
+
+ text: control.placeholderText
+ font: control.font
+ color: control.placeholderTextColor
+ verticalAlignment: control.verticalAlignment
+ visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter)
+ elide: Text.ElideRight
+ renderType: control.renderType
+ }
+
+ background: BackgroundDelegate {
+ parentControl: control
+ backgroundProperties: styleReader.background
+ }
+}
diff --git a/src/labs/stylekit/impl/BackgroundAndIndicatorDelegate.qml b/src/labs/stylekit/impl/BackgroundAndIndicatorDelegate.qml
new file mode 100644
index 0000000000..4e9f5d81e3
--- /dev/null
+++ b/src/labs/stylekit/impl/BackgroundAndIndicatorDelegate.qml
@@ -0,0 +1,94 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+/*
+ This delegate is a composition of the background and the indicator into
+ a single delegate. This is needed by some controls, since they only have
+ a background delegate, which is responsible for also drawing the "indicator".
+ An example is a Slider, which draws both the background, groove and track in
+ the background delegate.
+*/
+Item {
+ id: root
+ implicitWidth: !vertical
+ ? Math.max(background.implicitWidth,
+ indicatorLayout.implicitWidth)
+ : Math.max(background.implicitHeight,
+ indicatorLayout.implicitHeight)
+ implicitHeight: !vertical
+ ? Math.max(background.implicitHeight,
+ indicatorLayout.implicitHeight)
+ : Math.max(background.implicitWidth,
+ indicatorLayout.implicitWidth)
+
+ required property StyleKitDelegateProperties indicatorProperties
+ required property StyleKitDelegateProperties backgroundProperties
+ required property T.Control parentControl
+ property alias indicator: indicator
+ property bool vertical: false
+
+ StyleKitLayout {
+ id: indicatorLayout
+ container: root
+ contentMargins {
+ left: parentControl.leftPadding
+ right: parentControl.rightPadding
+ top: parentControl.topPadding
+ bottom: parentControl.bottomPadding
+ }
+ layoutItems: [
+ StyleKitLayoutItem {
+ id: indicatorItem
+ item: root.indicator
+ alignment: indicatorProperties.alignment
+ margins.left: indicatorProperties.leftMargin
+ margins.right: indicatorProperties.rightMargin
+ margins.top: indicatorProperties.topMargin
+ margins.bottom: indicatorProperties.bottomMargin
+ fillWidth: indicatorProperties.implicitWidth === Style.Stretch
+ fillHeight: indicatorProperties.implicitHeight === Style.Stretch
+ }
+ ]
+ mirrored: parentControl.mirrored
+ }
+
+ states: State {
+ /* The delegate logic is moved out of the delegate, to relieve the style
+ * developer from having to re-invent it if he changes the delegate. To
+ * disable it, 'states' can be set to an empty array from the outside. */
+ when: true
+ PropertyChanges {
+ background.parent: root
+ background.x: parentControl.leftInset
+ background.y: parentControl.topInset
+ background.width: root.width - parentControl.leftInset - parentControl.rightInset
+ background.height: root.height - parentControl.topInset - parentControl.bottomInset
+ }
+ }
+
+ BackgroundDelegate {
+ id: background
+ parentControl: root.parentControl
+ delegateProperties: root.backgroundProperties
+ }
+
+ IndicatorDelegate {
+ id: indicator
+ parentControl: root.parentControl
+ indicatorProperties: root.indicatorProperties
+ vertical: parentControl.vertical
+ z: 1
+ x: !vertical ? indicatorItem.x : parentControl.leftPadding + (parentControl.availableWidth - height) / 2
+ y: !vertical ? indicatorItem.y : parentControl.topPadding + width
+ width: !vertical ? indicatorItem.width : __stretchBgWidth ? parentControl.availableHeight : implicitWidth//indicatorItem.height
+ height: !vertical ? indicatorItem.height : __stretchBgHeight ? parentControl.availableWidth : implicitHeight//indicatorItem.width
+ readonly property bool __stretchBgWidth: root.indicatorProperties.implicitWidth === Style.Stretch
+ readonly property bool __stretchBgHeight: root.indicatorProperties.implicitHeight === Style.Stretch
+ }
+}
diff --git a/src/labs/stylekit/impl/BackgroundDelegate.qml b/src/labs/stylekit/impl/BackgroundDelegate.qml
new file mode 100644
index 0000000000..3704f3fdac
--- /dev/null
+++ b/src/labs/stylekit/impl/BackgroundDelegate.qml
@@ -0,0 +1,12 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+DelegateContainer {
+ required property StyleKitDelegateProperties backgroundProperties
+ delegateProperties: backgroundProperties
+}
diff --git a/src/labs/stylekit/impl/CMakeLists.txt b/src/labs/stylekit/impl/CMakeLists.txt
new file mode 100644
index 0000000000..14f2849e9e
--- /dev/null
+++ b/src/labs/stylekit/impl/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## StyleKitImpl Module:
+#####################################################################
+
+set_source_files_properties(DelegateSingleton.qml
+ PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
+
+qt_internal_add_qml_module(QtQuickStyleKitImpl
+ URI Qt.labs.StyleKit.impl
+ DEPENDENCIES
+ QtQuick/auto
+ SOURCES
+ qqstylekitlayout_p.h qqstylekitlayout.cpp
+ QML_FILES
+ BackgroundAndIndicatorDelegate.qml
+ BackgroundDelegate.qml
+ DelegateContainer.qml
+ DelegateSingleton.qml
+ FallbackStyle.qml
+ HandleDelegate.qml
+ IndicatorDelegate.qml
+ Shadow.qml
+ LIBRARIES
+ Qt6::GuiPrivate
+ Qt6::Quick
+ Qt6::QuickPrivate
+ Qt6::QuickTemplates2
+ Qt6::QuickTemplates2Private
+)
diff --git a/src/labs/stylekit/impl/DelegateContainer.qml b/src/labs/stylekit/impl/DelegateContainer.qml
new file mode 100644
index 0000000000..496b4f9f5e
--- /dev/null
+++ b/src/labs/stylekit/impl/DelegateContainer.qml
@@ -0,0 +1,171 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Effects
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+// This container takes care of loading, positioning, and resizing the delegate.
+Item {
+ id: root
+
+ /* If an implicit size has been set the style, it wins.
+ * Otherwise we use the implicit size of the instantiated delegate. */
+ implicitWidth: delegateProperties.implicitWidth > 0
+ ? delegateProperties.implicitWidth
+ : delegateInstance
+ ? delegateInstance.implicitWidth
+ : 0
+
+ implicitHeight: delegateProperties.implicitHeight > 0
+ ? delegateProperties.implicitHeight
+ : delegateInstance
+ ? delegateInstance.implicitHeight
+ : 0
+
+ scale: delegateProperties.scale
+ rotation: delegateProperties.rotation
+ visible: delegateProperties.visible
+
+ required property QtObject parentControl
+ required property StyleKitDelegateProperties delegateProperties
+
+ property real leftMargin: delegateProperties.leftMargin
+ property real rightMargin: delegateProperties.rightMargin
+ property real topMargin: delegateProperties.topMargin
+ property real bottomMargin: delegateProperties.bottomMargin
+
+ readonly property real marginWidth: leftMargin + rightMargin
+ readonly property real marginHeight: topMargin + bottomMargin
+ readonly property real subDelegateWidth: width
+ readonly property real subDelegateHeight: height
+
+ onDelegatePropertiesChanged: {
+ instantiateDelegate()
+ instantiateShadowDelegate();
+ }
+
+ onVisibleChanged: {
+ instantiateDelegate()
+ instantiateShadowDelegate();
+ }
+
+ Connections {
+ target: delegateProperties
+ function onDelegateChanged() { instantiateDelegate() }
+ }
+
+ Connections {
+ target: delegateProperties.shadow
+ function onDelegateChanged() { instantiateShadowDelegate() }
+ function onColorChanged() { instantiateShadowDelegate() }
+ function onOpacityChanged() { instantiateShadowDelegate() }
+ function onVisibleChanged() { instantiateShadowDelegate() }
+ }
+
+ Item {
+ id: centerContainer
+ width: root.subDelegateWidth
+ height: root.subDelegateHeight
+ }
+
+ // Uncomment to draw overlay:
+ // Rectangle {
+ // id: debugRectForMargins
+ // x: centerContainer.x
+ // y: centerContainer.y
+ // z: 5
+ // width: root.subDelegateWidth
+ // height: root.subDelegateHeight
+ // color: "transparent"
+ // border.color: "limegreen"
+ // border.width: 1
+ // }
+
+ // Rectangle {
+ // id: debugRectForDelegate
+ // z: 5
+ // width: root.width
+ // height: root.height
+ // color: "transparent"
+ // border.color: "magenta"
+ // border.width: 1
+ // }
+
+ // ------------------------------------------
+
+ property Item delegateInstance: null
+ property Component effectiveDelegate: null
+
+ // onImplicitWidthChanged: {
+ // if (parentControl && parentControl instanceof T.CheckBox) {
+ // print("DelegateContainer delegateInstance changed to " + delegateInstance)
+ // print("delegate instance implicitWidth: " + (delegateInstance ? delegateInstance.implicitWidth : "N/A"))
+ // }
+ // }
+ // onWidthChanged: {
+ // if (parentControl && parentControl instanceof T.CheckBox) {
+ // print("DelegateContainer width changed to " + width)
+ // print("delegate instance width: " + (delegateInstance ? delegateInstance.width : "N/A"))
+ // }
+ // }
+
+ function instantiateDelegate() {
+ if (!visible || !StyleKit.styleLoaded())
+ return
+
+ let delegate = root.delegateProperties.delegate
+ if (!delegate)
+ delegate = DelegateSingleton.defaultDelegate
+ if (delegate === effectiveDelegate)
+ return
+
+ effectiveDelegate = delegate
+ let prevInstance = delegateInstance
+ if (delegate)
+ delegateInstance = delegate
+ ? delegate.createObject(
+ centerContainer,
+ { delegateProperties: root.delegateProperties, control: root.parentControl })
+ : null
+
+ if (prevInstance)
+ prevInstance.destroy()
+ }
+
+ // ------------------------------------------
+
+ property Item shadowInstance: null
+ property Component effectiveShadowDelegate: null
+
+ function instantiateShadowDelegate() {
+ if (!visible || !StyleKit.styleLoaded())
+ return
+
+ const shadowProps = root.delegateProperties.shadow
+ const delegate = shadowProps.delegate
+ ? shadowProps.delegate : DelegateSingleton.defaultShadowDelegate
+ if (delegate === effectiveShadowDelegate)
+ return
+
+ // Delay instantiating delegate until needed
+ if (!shadowProps.visible || shadowProps.color.a === 0 || shadowProps.opacity === 0)
+ return
+
+ let prevDelegateInstance = shadowInstance
+ effectiveShadowDelegate = delegate
+ shadowInstance = delegate
+ ? delegate.createObject(
+ root,
+ { z: -1, delegateProperties: root.delegateProperties, control: root.parentControl })
+ : null
+
+ if (prevDelegateInstance)
+ prevDelegateInstance.destroy()
+ }
+
+}
diff --git a/src/labs/stylekit/impl/DelegateSingleton.qml b/src/labs/stylekit/impl/DelegateSingleton.qml
new file mode 100644
index 0000000000..c119a5b9b6
--- /dev/null
+++ b/src/labs/stylekit/impl/DelegateSingleton.qml
@@ -0,0 +1,14 @@
+// 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
+// Qt-Security score:significant reason:default
+
+pragma Singleton
+
+import QtQuick
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+QtObject {
+ readonly property Component defaultDelegate: StyleKitDelegate {}
+ readonly property Component defaultShadowDelegate: Shadow {}
+}
diff --git a/src/labs/stylekit/impl/FallbackStyle.qml b/src/labs/stylekit/impl/FallbackStyle.qml
new file mode 100644
index 0000000000..d989fcc63a
--- /dev/null
+++ b/src/labs/stylekit/impl/FallbackStyle.qml
@@ -0,0 +1,226 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import Qt.labs.StyleKit
+
+BaseStyle {
+ id: style
+
+ /* Properties and Controls left unspecified in a Style will be read from Style.fallbackStyle
+ * instead (that is, this file, unless the fallback style is changed). Here we can give the
+ * properties some sensible defaults. */
+
+ readonly property real indicatorSize: 24
+
+ control {
+ spacing: 5
+ padding: 5
+
+ background {
+ implicitWidth: 100
+ implicitHeight: 40
+ border.width: 1
+ border.color: "black"
+ color: style.palette.base
+ }
+
+ indicator {
+ implicitWidth: style.indicatorSize
+ implicitHeight: style.indicatorSize
+ color: style.palette.base
+ border.color: "black"
+ border.width: 1
+ foreground {
+ implicitWidth: Style.Stretch
+ implicitHeight: Style.Stretch
+ margins: 1
+ color: style.palette.accent
+ image.color: style.palette.accent
+ /* Note: don't set implicit size here, since the DelegateContainer will (and should)
+ * fall back to use the size of the image if not set. So if we hard-code a size here,
+ * it cannot be unset to be the size of the image (if any) again from the Style. */
+ }
+ }
+
+ handle {
+ implicitWidth: style.indicatorSize
+ implicitHeight: style.indicatorSize
+ radius: style.indicatorSize / 2
+ border.width: 1
+ border.color: "black"
+ color: style.palette.window
+ }
+ }
+
+ textInput {
+ padding: 5
+ background {
+ implicitWidth: 150
+ implicitHeight: 40
+ border.width: 1
+ border.color: "black"
+ color: style.palette.base
+ }
+ }
+
+ popup {
+ background {
+ implicitWidth: 200
+ implicitHeight: 200
+ border.width: 1
+ border.color: "black"
+ color: style.palette.base
+ }
+ }
+
+ pane {
+ background {
+ implicitWidth: 200
+ implicitHeight: 200
+ color: style.palette.window
+ }
+ }
+
+ page {
+ background.border.width: 0
+ }
+
+ frame {
+ background.color: style.palette.base
+ }
+
+ flatButton {
+ background.visible: false
+ hovered.background.visible: true
+ }
+
+ checkBox {
+ background.visible: false
+ indicator {
+ foreground {
+ visible: false
+ color: "transparent"
+ image.source: "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png"
+ }
+ }
+ text.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ checked.indicator.foreground.visible: true
+ }
+
+ comboBox {
+ text.alignment: Qt.AlignVCenter
+ background.implicitWidth: 150
+ indicator {
+ color: style.palette.base
+ border.width: 0
+ foreground {
+ margins: 4
+ color: "transparent"
+ image.source: "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/drop-indicator.png"
+ image.color: style.palette.buttonText
+ }
+ }
+ }
+
+ spinBox {
+ text.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ indicator {
+ implicitHeight: Style.Stretch
+ color: style.palette.base
+ border.width: 0
+ margins: 0
+
+ foreground {
+ color: "transparent"
+ image.fillMode: Image.PreserveAspectFit
+ image.source: "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/arrow-indicator.png"
+ image.color: "black"
+
+ // Note: by setting a rotation, we cannot at the same time set the implicit size to
+ // Stretch, since Stretch will be based on the unrotated geometry of the indicator.
+ // So if the height of the indicator is different from the width, things will look wrong.
+ implicitWidth: 14
+ implicitHeight: 14
+ alignment: Qt.AlignCenter
+ }
+
+ down {
+ alignment: Qt.AlignLeft
+ foreground.rotation: 90
+ }
+
+ up {
+ alignment: Qt.AlignRight
+ foreground.rotation: -90
+ }
+ }
+ }
+
+ radioButton {
+ background.visible: false
+ indicator {
+ radius: 255
+ foreground {
+ margins: 4
+ visible: false
+ radius: 255
+ }
+ }
+ text.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ checked.indicator.foreground.visible: true
+ }
+
+ itemDelegate {
+ text.alignment: Qt.AlignVCenter | Qt.AlignLeft
+ background {
+ // Make it flat
+ color: palette.base
+ border.width: 0
+ shadow.visible: false
+ }
+ hovered.background.color: palette.highlight
+ }
+
+ textField {
+ text.alignment: Qt.AlignVCenter
+ background {
+ implicitWidth: 150
+ color: style.palette.base
+ gradient: null
+ }
+ }
+
+ slider {
+ background {
+ visible: false
+ implicitWidth: 150
+ }
+ indicator {
+ implicitWidth: Style.Stretch
+ implicitHeight: 8
+ radius: 8
+ foreground {
+ radius: 7
+ }
+ }
+ }
+
+ switchControl {
+ background.visible: false
+ text.alignment: Qt.AlignVCenter
+ indicator {
+ implicitWidth: style.indicatorSize * 2
+ implicitHeight: style.indicatorSize
+ alignment: Qt.AlignLeft | Qt.AlignVCenter
+ radius: style.indicatorSize / 2
+ foreground {
+ color: style.palette.base
+ radius: style.indicatorSize / 2
+ }
+ }
+ checked.indicator.foreground.color: style.palette.accent
+ }
+
+}
diff --git a/src/labs/stylekit/impl/HandleDelegate.qml b/src/labs/stylekit/impl/HandleDelegate.qml
new file mode 100644
index 0000000000..73ab71c332
--- /dev/null
+++ b/src/labs/stylekit/impl/HandleDelegate.qml
@@ -0,0 +1,22 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+DelegateContainer {
+ /* The margins are not added to the handle itself, but to
+ * the area (groove) where the handle moves. This is taken
+ * care of by the control that uses the handle */
+ leftMargin: 0
+ rightMargin: 0
+ topMargin: 0
+ bottomMargin: 0
+
+ delegateProperties: handleProperties
+
+ required property StyleKitDelegateProperties handleProperties
+}
diff --git a/src/labs/stylekit/impl/IndicatorDelegate.qml b/src/labs/stylekit/impl/IndicatorDelegate.qml
new file mode 100644
index 0000000000..634d6bdedf
--- /dev/null
+++ b/src/labs/stylekit/impl/IndicatorDelegate.qml
@@ -0,0 +1,76 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Templates as T
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+DelegateContainer {
+ id: root
+
+ implicitWidth: Math.max(_delegateImplicitWidth, indicatorLayout.implicitWidth)
+ implicitHeight: Math.max(_delegateImplicitHeight, indicatorLayout.implicitHeight)
+
+ transformOrigin: Item.TopLeft
+ rotation: vertical ? 90 : 0
+ scale: vertical ? -1 : 1
+ delegateProperties: root.indicatorProperties
+
+ required property QtObject parentControl
+ required property StyleKitDelegateProperties indicatorProperties
+ property bool vertical: false
+ /* Some indicators (Slider, RangeSlider) should let the foreground delegate
+ * only fill up a certain amount of the available foreground space (that is, the
+ * track / progress). This amount be controlled with firstProgress and secondProgress. */
+ property real firstProgress: 0.0
+ property real secondProgress: 1.0
+
+ readonly property real _delegateImplicitWidth: root.indicatorProperties.implicitWidth > 0
+ ? root.indicatorProperties.implicitWidth
+ : delegateInstance
+ ? delegateInstance.implicitWidth
+ : 0
+ readonly property real _delegateImplicitHeight: root.indicatorProperties.implicitHeight > 0
+ ? root.indicatorProperties.implicitHeight
+ : delegateInstance
+ ? delegateInstance.implicitHeight
+ : 0
+
+ StyleKitLayout {
+ id: indicatorLayout
+ container: root
+ enabled: true
+ layoutItems: [
+ StyleKitLayoutItem {
+ id: fgItem
+ item: foreground
+ alignment: indicatorProperties.foreground.alignment
+ margins.left: indicatorProperties.foreground.leftMargin
+ margins.right: indicatorProperties.foreground.rightMargin
+ margins.top: indicatorProperties.foreground.topMargin
+ margins.bottom: indicatorProperties.foreground.bottomMargin
+ fillWidth: indicatorProperties.foreground.implicitWidth === Style.Stretch
+ fillHeight: indicatorProperties.foreground.implicitHeight === Style.Stretch
+ }
+ ]
+ mirrored: parentControl.mirrored
+ }
+
+ DelegateContainer {
+ id: foreground
+ parent: root
+ parentControl: root.parentControl
+ delegateProperties: root.indicatorProperties.foreground
+ x: fgItem.x + firstProgress * (fgItem.fillWidth
+ ? fgItem.width - indicatorProperties.foreground.minimumWidth
+ : fgItem.width)
+ y: fgItem.y
+ width: fgItem.fillWidth ? (indicatorProperties.foreground.minimumWidth
+ + ((secondProgress - firstProgress) * (fgItem.width
+ - indicatorProperties.foreground.minimumWidth)))
+ : (secondProgress - firstProgress) * fgItem.width
+ height: fgItem.height
+ }
+}
diff --git a/src/labs/stylekit/impl/Shadow.qml b/src/labs/stylekit/impl/Shadow.qml
new file mode 100644
index 0000000000..6014ea7224
--- /dev/null
+++ b/src/labs/stylekit/impl/Shadow.qml
@@ -0,0 +1,28 @@
+// 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
+// Qt-Security score:significant reason:default
+
+import QtQuick
+import QtQuick.Effects
+import Qt.labs.StyleKit
+import Qt.labs.StyleKit.impl
+
+RectangularShadow {
+ required property QtObject control
+ required property StyleKitDelegateProperties delegateProperties
+
+ width: parent.width
+ height: parent.height
+ color: delegateProperties.shadow.color
+ opacity: delegateProperties.shadow.opacity
+ blur: delegateProperties.shadow.blur
+ scale: delegateProperties.shadow.scale
+ radius: delegateProperties.radius
+ topLeftRadius: delegateProperties.topLeftRadius
+ topRightRadius: delegateProperties.topRightRadius
+ bottomLeftRadius: delegateProperties.bottomLeftRadius
+ bottomRightRadius: delegateProperties.bottomRightRadius
+ offset: Qt.vector2d(
+ delegateProperties.shadow.horizontalOffset,
+ delegateProperties.shadow.verticalOffset)
+}
diff --git a/src/labs/stylekit/impl/qqstylekitlayout.cpp b/src/labs/stylekit/impl/qqstylekitlayout.cpp
new file mode 100644
index 0000000000..f29bcc4c57
--- /dev/null
+++ b/src/labs/stylekit/impl/qqstylekitlayout.cpp
@@ -0,0 +1,538 @@
+// 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
+
+#include "qqstylekitlayout_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static qreal width(QQStyleKitLayoutItem *li, qreal availableWidth = .0)
+{
+ Q_ASSERT(li);
+ Q_ASSERT(li->item());
+ QQuickItem *item = li->item();
+ qreal w = item->implicitWidth();
+ if (li->fillWidth())
+ w = qMax(.0, availableWidth - li->margins().left() - li->margins().right());
+ return qMax(.0, w);
+}
+
+static qreal height(QQStyleKitLayoutItem *li, qreal availableHeight = .0)
+{
+ Q_ASSERT(li);
+ Q_ASSERT(li->item());
+ QQuickItem *item = li->item();
+ qreal h = item->implicitHeight();
+ if (li->fillHeight())
+ h = qMax(.0, availableHeight - li->margins().top() - li->margins().bottom());
+ return qMax(.0, h);
+}
+
+static qreal totalWidth(const QVector<QQStyleKitLayoutItem *> &items, qreal spacing)
+{
+ qreal total = .0;
+ for (QQStyleKitLayoutItem *li : items) {
+ if (li->item() && li->item()->isVisible())
+ total += width(li) + li->margins().left() + li->margins().right() + spacing;
+ }
+ return total;
+}
+
+static qreal totalHeight(const QVector<QQStyleKitLayoutItem *> &items)
+{
+ qreal maxHeight = .0;
+ for (QQStyleKitLayoutItem *li : items) {
+ if (li->item() && li->item()->isVisible()) {
+ const qreal h = height(li) + li->margins().top() + li->margins().bottom();
+ if (h > maxHeight)
+ maxHeight = h;
+ }
+ }
+ return maxHeight;
+}
+
+static qreal vAlignY(QQStyleKitLayoutItem *li, qreal containerY, qreal containerHeight)
+{
+ Q_ASSERT(li);
+ Q_ASSERT(li->item());
+
+ const auto itemHeight = height(li, containerHeight);
+ const auto vAlign = li->alignment() & Qt::AlignVertical_Mask;
+ const auto margins = li->margins();
+ switch (vAlign) {
+ case Qt::AlignTop:
+ return containerY + margins.top();
+ case Qt::AlignBottom:
+ return containerY + containerHeight - itemHeight - margins.bottom();
+ default: // AlignVCenter
+ return containerY + margins.top()
+ + (containerHeight - margins.top() - margins.bottom() - itemHeight) / 2.0;
+ }
+}
+
+QQStyleKitLayoutItem::QQStyleKitLayoutItem(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QQuickItem *QQStyleKitLayoutItem::item() const
+{
+ return m_item;
+}
+
+void QQStyleKitLayoutItem::setItem(QQuickItem *item)
+{
+ if (m_item == item)
+ return;
+
+ if (m_item)
+ disconnect(m_item, nullptr, this, nullptr);
+
+ m_item = item;
+ if (m_item) {
+ connect(m_item, &QQuickItem::implicitWidthChanged, this, [this]() { emit itemChanged(); });
+ connect(m_item, &QQuickItem::implicitHeightChanged, this, [this]() { emit itemChanged(); });
+ connect(m_item, &QQuickItem::visibleChanged, this, [this]() { emit itemChanged(); });
+ }
+ // TODO: parentchanged
+ emit itemChanged();
+}
+
+qreal QQStyleKitLayoutItem::x() const
+{
+ return m_x;
+}
+
+void QQStyleKitLayoutItem::setX(qreal x)
+{
+ if (qFuzzyCompare(m_x, x))
+ return;
+
+ m_x = x;
+ emit xChanged();
+}
+
+qreal QQStyleKitLayoutItem::y() const
+{
+ return m_y;
+}
+
+void QQStyleKitLayoutItem::setY(qreal y)
+{
+ if (qFuzzyCompare(m_y, y))
+ return;
+
+ m_y = y;
+ emit yChanged();
+}
+
+qreal QQStyleKitLayoutItem::width() const
+{
+ return m_width;
+}
+
+void QQStyleKitLayoutItem::setWidth(qreal width)
+{
+ if (qFuzzyCompare(m_width, width))
+ return;
+
+ m_width = width;
+ emit widthChanged();
+}
+
+qreal QQStyleKitLayoutItem::height() const
+{
+ return m_height;
+}
+
+void QQStyleKitLayoutItem::setHeight(qreal height)
+{
+ if (qFuzzyCompare(m_height, height))
+ return;
+
+ m_height = height;
+ emit heightChanged();
+}
+
+Qt::Alignment QQStyleKitLayoutItem::alignment() const
+{
+ return m_alignment;
+}
+
+void QQStyleKitLayoutItem::setAlignment(Qt::Alignment alignment)
+{
+ if (m_alignment == alignment)
+ return;
+
+ m_alignment = alignment;
+ emit alignmentChanged();
+}
+
+QMarginsF QQStyleKitLayoutItem::margins() const
+{
+ return m_margins;
+}
+
+void QQStyleKitLayoutItem::setMargins(const QMarginsF &margins)
+{
+ if (m_margins == margins)
+ return;
+
+ m_margins = margins;
+ emit marginsChanged();
+}
+
+bool QQStyleKitLayoutItem::fillWidth() const
+{
+ return m_fillWidth;
+}
+
+void QQStyleKitLayoutItem::setFillWidth(bool fill)
+{
+ if (m_fillWidth == fill)
+ return;
+
+ m_fillWidth = fill;
+ emit fillWidthChanged();
+}
+
+bool QQStyleKitLayoutItem::fillHeight() const
+{
+ return m_fillHeight;
+}
+
+void QQStyleKitLayoutItem::setFillHeight(bool fill)
+{
+ if (m_fillHeight == fill)
+ return;
+
+ m_fillHeight = fill;
+ emit fillHeightChanged();
+}
+
+QQStyleKitLayout::QQStyleKitLayout(QObject *parent)
+ : QObject(parent)
+ , m_mirrored(false)
+ , m_enabled(true)
+ , m_updatingLayout(false)
+{
+ m_updateTimer.setSingleShot(true);
+ connect(&m_updateTimer, &QTimer::timeout, this, &QQStyleKitLayout::updateLayout);
+}
+
+QQuickItem *QQStyleKitLayout::container() const
+{
+ return m_container;
+}
+
+void QQStyleKitLayout::setContainer(QQuickItem *container)
+{
+ if (m_container == container)
+ return;
+
+ m_container = container;
+ emit containerChanged();
+ connect(m_container, &QQuickItem::widthChanged, this, &QQStyleKitLayout::scheduleUpdate);
+ connect(m_container, &QQuickItem::heightChanged, this, &QQStyleKitLayout::scheduleUpdate);
+
+ scheduleUpdate();
+}
+
+QQmlListProperty<QQStyleKitLayoutItem> QQStyleKitLayout::layoutItems()
+{
+ return QQmlListProperty<QQStyleKitLayoutItem>(const_cast<QQStyleKitLayout *>(this),
+ nullptr,
+ &QQStyleKitLayout::layoutItem_append,
+ &QQStyleKitLayout::layoutItem_count,
+ &QQStyleKitLayout::layoutItem_at,
+ &QQStyleKitLayout::layoutItem_clear);
+}
+
+void QQStyleKitLayout::layoutItem_append(QQmlListProperty<QQStyleKitLayoutItem> *list, QQStyleKitLayoutItem *item)
+{
+ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
+ if (layout && item) {
+ layout->m_layoutItems.append(item);
+ connect(item, &QQStyleKitLayoutItem::itemChanged, layout, &QQStyleKitLayout::scheduleUpdate);
+ connect(item, &QQStyleKitLayoutItem::alignmentChanged, layout, &QQStyleKitLayout::scheduleUpdate);
+ connect(item, &QQStyleKitLayoutItem::marginsChanged, layout, &QQStyleKitLayout::scheduleUpdate);
+ connect(item, &QQStyleKitLayoutItem::fillWidthChanged, layout, &QQStyleKitLayout::scheduleUpdate);
+ connect(item, &QQStyleKitLayoutItem::fillHeightChanged, layout, &QQStyleKitLayout::scheduleUpdate);
+ emit layout->layoutItemsChanged();
+ layout->scheduleUpdate();
+ }
+}
+
+qsizetype QQStyleKitLayout::layoutItem_count(QQmlListProperty<QQStyleKitLayoutItem> *list)
+{
+ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
+ if (layout)
+ return layout->m_layoutItems.size();
+ return 0;
+}
+
+QQStyleKitLayoutItem *QQStyleKitLayout::layoutItem_at(QQmlListProperty<QQStyleKitLayoutItem> *list, qsizetype index)
+{
+ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
+ if (layout)
+ return layout->m_layoutItems.value(index);
+ return nullptr;
+}
+
+void QQStyleKitLayout::layoutItem_clear(QQmlListProperty<QQStyleKitLayoutItem> *list)
+{
+ QQStyleKitLayout *layout = qobject_cast<QQStyleKitLayout *>(list->object);
+ if (layout) {
+ layout->m_layoutItems.clear();
+ emit layout->layoutItemsChanged();
+ layout->scheduleUpdate();
+ }
+}
+
+QMarginsF QQStyleKitLayout::padding() const
+{
+ return m_padding;
+}
+
+QMarginsF QQStyleKitLayout::contentMargins() const
+{
+ return m_contentMargins;
+}
+
+void QQStyleKitLayout::setContentMargins(const QMarginsF &margins)
+{
+ if (m_contentMargins == margins)
+ return;
+
+ m_contentMargins = margins;
+ emit contentMarginsChanged();
+ scheduleUpdate();
+}
+
+qreal QQStyleKitLayout::spacing() const
+{
+ return m_spacing;
+}
+
+void QQStyleKitLayout::setSpacing(qreal spacing)
+{
+ if (qFuzzyCompare(m_spacing, spacing))
+ return;
+
+ m_spacing = spacing;
+ emit spacingChanged();
+ scheduleUpdate();
+}
+
+bool QQStyleKitLayout::isMirrored() const
+{
+ return m_mirrored;
+}
+
+void QQStyleKitLayout::setMirrored(bool mirrored)
+{
+ if (m_mirrored == mirrored)
+ return;
+
+ m_mirrored = mirrored;
+ emit mirroredChanged();
+ scheduleUpdate();
+}
+
+qreal QQStyleKitLayout::implicitWidth() const
+{
+ return m_implicitWidth;
+}
+
+qreal QQStyleKitLayout::implicitHeight() const
+{
+ return m_implicitHeight;
+}
+
+void QQStyleKitLayout::setImplicitWidth(qreal width)
+{
+ if (qFuzzyCompare(m_implicitWidth, width))
+ return;
+
+ m_implicitWidth = width;
+ emit implicitWidthChanged();
+ scheduleUpdate();
+}
+
+void QQStyleKitLayout::setImplicitHeight(qreal height)
+{
+ if (qFuzzyCompare(m_implicitHeight, height))
+ return;
+
+ m_implicitHeight = height;
+ emit implicitHeightChanged();
+ scheduleUpdate();
+}
+
+bool QQStyleKitLayout::isEnabled() const
+{
+ return m_enabled;
+}
+
+void QQStyleKitLayout::setEnabled(bool enabled)
+{
+ if (m_enabled == enabled)
+ return;
+
+ m_enabled = enabled;
+ emit enabledChanged();
+
+ if (m_enabled)
+ scheduleUpdate();
+}
+
+void QQStyleKitLayout::updateLayout()
+{
+ if (!m_enabled)
+ return;
+
+ if (!m_container || m_container->width() <= 0 || m_container->height() <= 0)
+ return;
+
+ if (m_updatingLayout)
+ return;
+ m_updatingLayout = true;
+
+ QVector<QQStyleKitLayoutItem *> left;
+ QVector<QQStyleKitLayoutItem *> right;
+ QVector<QQStyleKitLayoutItem *> center;
+
+ for (QQStyleKitLayoutItem *li : m_layoutItems) {
+ if (!li->item() || !li->item()->isVisible())
+ continue;
+ const auto hAlign = li->alignment() & Qt::AlignHorizontal_Mask;
+ const bool isMirrored = m_mirrored && !(hAlign & Qt::AlignAbsolute);
+ switch (hAlign) {
+ case Qt::AlignLeft:
+ if (isMirrored)
+ right.append(li);
+ else
+ left.append(li);
+ break;
+ case Qt::AlignRight:
+ if (isMirrored)
+ left.append(li);
+ else
+ right.append(li);
+ break;
+ default:
+ center.append(li);
+ break;
+ }
+ }
+
+ const qreal containerWidth = m_container->width() ? m_container->width() : m_container->implicitWidth();
+ const qreal containerHeight = m_container->height() ? m_container->height() : m_container->implicitHeight();
+ const qreal paddedX = m_contentMargins.left();
+ const qreal paddedY = m_contentMargins.top();
+ const qreal paddedWidth = qMax(containerWidth - m_contentMargins.left() - m_contentMargins.right(), .0);
+ const qreal paddedHeight = qMax(containerHeight - m_contentMargins.top() - m_contentMargins.bottom(), .0);
+
+ // Position left-aligned items
+ {
+ qreal x = paddedX;
+ for (QQStyleKitLayoutItem *li : left) {
+ QQuickItem *item = li->item();
+ if (!item || !item->isVisible())
+ continue;
+
+ const QMarginsF margins = li->margins();
+ const qreal itemWidth = width(li, paddedWidth);
+ const qreal itemHeight = height(li, paddedHeight);
+ auto y = vAlignY(li, paddedY, paddedHeight);
+ li->setX(x + margins.left());
+ li->setY(y);
+ li->setWidth(itemWidth);
+ li->setHeight(itemHeight);
+ x += itemWidth + margins.left() + margins.right() + m_spacing;
+ }
+ }
+
+ // Position right-aligned items
+ {
+ qreal x = paddedX + paddedWidth;
+ for (QQStyleKitLayoutItem *li : right) {
+ QQuickItem *item = li->item();
+ if (!item || !item->isVisible())
+ continue;
+
+ const QMarginsF margins = li->margins();
+ const qreal itemWidth = width(li, paddedWidth);
+ const qreal itemHeight = height(li, paddedHeight);
+ x -= itemWidth + margins.right() + margins.left();
+ auto y = vAlignY(li, paddedY, paddedHeight);
+ li->setX(x + margins.left());
+ li->setY(y);
+ li->setWidth(itemWidth);
+ li->setHeight(itemHeight);
+ x -= m_spacing;
+ }
+ }
+
+ // Position center-aligned items
+ {
+ qreal x = paddedX + (paddedWidth - totalWidth(center, m_spacing)) / 2;
+ for (QQStyleKitLayoutItem *li : center) {
+ QQuickItem *item = li->item();
+ if (!item || !item->isVisible())
+ continue;
+
+ const QMarginsF margins = li->margins();
+ const qreal itemWidth = width(li, paddedWidth);
+ const qreal itemHeight = height(li, paddedHeight);
+ auto y = vAlignY(li, paddedY, paddedHeight);
+ li->setX(x + margins.left());
+ li->setY(y);
+ li->setWidth(itemWidth);
+ li->setHeight(itemHeight);
+ x += itemWidth + margins.left() + margins.right() + m_spacing;
+ }
+ }
+
+ const auto leftWidth = totalWidth(left, m_spacing);
+ const auto leftHeight = totalHeight(left);
+ const auto rightWidth = totalWidth(right, m_spacing);
+ const auto rightHeight = totalHeight(right);
+ const auto centerWidth = totalWidth(center, m_spacing);
+ const auto centerHeight = totalHeight(center);
+
+ const auto implicitWidth = leftWidth + rightWidth + centerWidth
+ - m_spacing * (left.isEmpty() ? 0 : 1)
+ - m_spacing * (right.isEmpty() ? 0 : 1)
+ - m_spacing * (center.isEmpty() ? 0 : 1)
+ + m_contentMargins.left() + m_contentMargins.right();
+ setImplicitWidth(implicitWidth);
+ const auto implicitHeight = qMax(qMax(leftHeight, rightHeight), centerHeight)
+ + m_contentMargins.top() + m_contentMargins.bottom();
+ setImplicitHeight(implicitHeight);
+
+ // HACK for control's contentItem
+ // QQuickControl determines the contentItem geometry based on the control size and padding
+ // So we include the layout's left/right widths into the padding calculation
+ auto leftPadding = m_contentMargins.left() + leftWidth;
+ auto topPadding = m_contentMargins.top(); // TODO: support vertical layout items
+ auto rightPadding = m_contentMargins.right() + rightWidth;
+ auto bottomPadding = m_contentMargins.bottom(); // TODO: support vertical layout items
+ if (isMirrored())
+ std::swap(leftPadding, rightPadding);
+ QMarginsF newPadding = QMarginsF(leftPadding, topPadding, rightPadding, bottomPadding);
+ if (m_padding != newPadding) {
+ m_padding = newPadding;
+ emit paddingChanged();
+ }
+ m_updatingLayout = false;
+}
+
+void QQStyleKitLayout::scheduleUpdate()
+{
+ if (!m_updateTimer.isActive())
+ m_updateTimer.start(0);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitlayout_p.cpp"
diff --git a/src/labs/stylekit/impl/qqstylekitlayout_p.h b/src/labs/stylekit/impl/qqstylekitlayout_p.h
new file mode 100644
index 0000000000..d6a0e74cd1
--- /dev/null
+++ b/src/labs/stylekit/impl/qqstylekitlayout_p.h
@@ -0,0 +1,168 @@
+// 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 QQUICKUNIFIEDLAYOUT_H
+#define QQUICKUNIFIEDLAYOUT_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 <QtQml/QtQml>
+#include <QtCore/qtimer.h>
+#include <QtQuickTemplates2/private/qquickcontrol_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitLayoutItem : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickItem *item READ item WRITE setItem NOTIFY itemChanged FINAL)
+ Q_PROPERTY(qreal x READ x NOTIFY xChanged FINAL)
+ Q_PROPERTY(qreal y READ y NOTIFY yChanged FINAL)
+ Q_PROPERTY(qreal width READ width NOTIFY widthChanged FINAL)
+ Q_PROPERTY(qreal height READ height NOTIFY heightChanged FINAL)
+ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL)
+ Q_PROPERTY(QMarginsF margins READ margins WRITE setMargins NOTIFY marginsChanged FINAL)
+ Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged FINAL)
+ Q_PROPERTY(bool fillHeight READ fillHeight WRITE setFillHeight NOTIFY fillHeightChanged FINAL)
+ QML_NAMED_ELEMENT(StyleKitLayoutItem)
+
+public:
+ QQStyleKitLayoutItem(QObject *parent = nullptr);
+ QQuickItem *item() const;
+ void setItem(QQuickItem *item);
+
+ qreal x() const;
+ void setX(qreal x);
+ qreal y() const;
+ void setY(qreal y);
+ qreal width() const;
+ void setWidth(qreal width);
+ qreal height() const;
+ void setHeight(qreal height);
+
+ Qt::Alignment alignment() const;
+ void setAlignment(Qt::Alignment alignment);
+
+ QMarginsF margins() const;
+ void setMargins(const QMarginsF &margins);
+
+ bool fillWidth() const;
+ void setFillWidth(bool fill);
+
+ bool fillHeight() const;
+ void setFillHeight(bool fill);
+
+signals:
+ void itemChanged();
+ void xChanged();
+ void yChanged();
+ void widthChanged();
+ void heightChanged();
+ void alignmentChanged();
+ void marginsChanged();
+ void fillWidthChanged();
+ void fillHeightChanged();
+
+private:
+ QPointer<QQuickItem> m_item;
+ Qt::Alignment m_alignment = Qt::AlignLeft | Qt::AlignVCenter;
+ QMarginsF m_margins;
+ bool m_fillWidth = false;
+ bool m_fillHeight = false;
+ qreal m_x = 0;
+ qreal m_y = 0;
+ qreal m_width = 0;
+ qreal m_height = 0;
+};
+
+class QQStyleKitLayout : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickItem *container READ container WRITE setContainer NOTIFY containerChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<QQStyleKitLayoutItem> layoutItems READ layoutItems NOTIFY layoutItemsChanged FINAL)
+ Q_PROPERTY(QMarginsF padding READ padding NOTIFY paddingChanged FINAL)
+ Q_PROPERTY(QMarginsF contentMargins READ contentMargins WRITE setContentMargins NOTIFY contentMarginsChanged FINAL)
+ Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged FINAL)
+ Q_PROPERTY(bool mirrored READ isMirrored WRITE setMirrored NOTIFY mirroredChanged FINAL)
+ Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged FINAL)
+ Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged FINAL)
+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL)
+ Q_CLASSINFO("DefaultProperty", "layoutItems")
+ QML_NAMED_ELEMENT(StyleKitLayout)
+
+public:
+ QQStyleKitLayout(QObject *parent = nullptr);
+
+ QQuickItem *container() const;
+ void setContainer(QQuickItem *item);
+
+ QQmlListProperty<QQStyleKitLayoutItem> layoutItems();
+
+ QMarginsF padding() const;
+
+ QMarginsF contentMargins() const;
+ void setContentMargins(const QMarginsF &margins);
+
+ qreal spacing() const;
+ void setSpacing(qreal spacing);
+
+ bool isMirrored() const;
+ void setMirrored(bool mirrored);
+
+ qreal implicitWidth() const;
+ qreal implicitHeight() const;
+
+ void setImplicitWidth(qreal width);
+ void setImplicitHeight(qreal height);
+
+ bool isEnabled() const;
+ void setEnabled(bool enabled);
+
+signals:
+ void containerChanged();
+ void layoutItemsChanged();
+ void layoutChanged();
+ void paddingChanged();
+ void contentMarginsChanged();
+ void spacingChanged();
+ void mirroredChanged();
+ void implicitWidthChanged();
+ void implicitHeightChanged();
+ void enabledChanged();
+
+private:
+ void updateLayout();
+ void scheduleUpdate();
+
+ static void layoutItem_append(QQmlListProperty<QQStyleKitLayoutItem> *list, QQStyleKitLayoutItem *item);
+ static qsizetype layoutItem_count(QQmlListProperty<QQStyleKitLayoutItem> *list);
+ static QQStyleKitLayoutItem *layoutItem_at(QQmlListProperty<QQStyleKitLayoutItem> *list, qsizetype index);
+ static void layoutItem_clear(QQmlListProperty<QQStyleKitLayoutItem> *list);
+
+ QPointer<QQuickItem> m_container;
+ QList<QQStyleKitLayoutItem *> m_layoutItems;
+ QMarginsF m_contentMargins;
+ QMarginsF m_padding;
+ qreal m_spacing = 0;
+ qreal m_implicitWidth = 0;;
+ qreal m_implicitHeight = 0;
+
+ bool m_mirrored: 1;
+ bool m_enabled: 1;
+ bool m_updatingLayout: 1;
+
+ QTimer m_updateTimer;
+};
+
+QT_END_NAMESPACE
+#endif // QQUICKUNIFIEDLAYOUT_H
diff --git a/src/labs/stylekit/qqstylekit.cpp b/src/labs/stylekit/qqstylekit.cpp
new file mode 100644
index 0000000000..6f7ad2f2f3
--- /dev/null
+++ b/src/labs/stylekit/qqstylekit.cpp
@@ -0,0 +1,131 @@
+// 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
+
+#include "qqstylekit_p.h"
+#include "qqstylekitstyle_p.h"
+#include "qqstylekittheme_p.h"
+#include "qqstylekitreader_p.h"
+
+#include <QtGui/QStyleHints>
+
+QT_BEGIN_NAMESPACE
+
+QPointer<QQStyleKitAttached> QQStyleKitAttached::s_instance;
+bool QQStyleKitAttached::s_transitionsEnabled = true;
+
+QQStyleKit::QQStyleKit(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QQStyleKitAttached *QQStyleKit::qmlAttachedProperties(QObject *obj)
+{
+ /* QQStyleKitAttached is a singleton. It doesn't matter where it's
+ * used from in the application, it will always represent the same
+ * application global style and theme. */
+ if (!QQStyleKitAttached::s_instance)
+ QQStyleKitAttached::s_instance = new QQStyleKitAttached(QGuiApplication::instance());
+ if (!QQStyleKitAttached::s_instance->m_engine && obj)
+ QQStyleKitAttached::s_instance->m_engine = qmlEngine(obj);
+ return QQStyleKitAttached::s_instance.get();
+}
+
+QQStyleKitAttached::QQStyleKitAttached(QObject *parent)
+ : QObject(parent)
+{
+ connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this](){
+ if (m_style)
+ m_style->recreateTheme();
+ });
+}
+
+QQStyleKitAttached::~QQStyleKitAttached()
+{
+ QQStyleKitAttached::s_instance = nullptr;
+ if (m_ownsStyle) {
+ delete m_style;
+ m_style = nullptr;
+ }
+}
+
+QQStyleKitStyle *QQStyleKitAttached::style() const
+{
+ return m_style;
+}
+
+void QQStyleKitAttached::setStyle(QQStyleKitStyle *style)
+{
+ if (m_style == style)
+ return;
+
+ if (m_ownsStyle)
+ m_style->deleteLater();
+
+ m_style = style;
+
+ if (m_style && m_style->m_theme)
+ m_style->m_theme->updateQuickTheme();
+ if (m_style->loaded())
+ QQStyleKitReader::resetAll();
+
+ emit styleChanged();
+}
+
+QString QQStyleKitAttached::styleUrl() const
+{
+ return m_styleUrl;
+}
+
+void QQStyleKitAttached::setStyleUrl(const QString &styleUrl)
+{
+ if (m_styleUrl == styleUrl)
+ return;
+
+ m_styleUrl = styleUrl;
+
+ Q_ASSERT(m_engine);
+ QQmlComponent comp(m_engine, QUrl(styleUrl), this);
+ if (!comp.errors().isEmpty()) {
+ qmlWarning(this) << "Could not create a StyleKit style: " << comp.errorString();
+ return;
+ }
+ auto *style = qobject_cast<QQStyleKitStyle *>(comp.create());
+ if (!style) {
+ qmlWarning(this) << "Could not create a StyleKit style from url: " << styleUrl;
+ return;
+ }
+
+ style->componentComplete();
+ setStyle(style);
+ m_ownsStyle = true;
+ emit styleUrlChanged();
+}
+
+bool QQStyleKitAttached::transitionsEnabled() const
+{
+ return s_transitionsEnabled;
+}
+
+void QQStyleKitAttached::setTransitionsEnabled(bool enabled)
+{
+ if (enabled == s_transitionsEnabled)
+ return;
+
+ s_transitionsEnabled = enabled;
+ emit transitionsEnabledChanged();
+}
+
+QQStyleKitDebug *QQStyleKitAttached::debug() const
+{
+ return &const_cast<QQStyleKitAttached *>(this)->m_debug;
+}
+
+bool QQStyleKitAttached::styleLoaded() const
+{
+ return m_style && m_style->loaded();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekit_p.cpp"
+
diff --git a/src/labs/stylekit/qqstylekit_p.h b/src/labs/stylekit/qqstylekit_p.h
new file mode 100644
index 0000000000..bd8263f767
--- /dev/null
+++ b/src/labs/stylekit/qqstylekit_p.h
@@ -0,0 +1,86 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKIT_P_H
+#define QQSTYLEKIT_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 "qqstylekitdebug_p.h"
+
+#include <QtQml/QtQml>
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitAttached;
+
+class QQStyleKit : public QObject
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(StyleKit)
+ QML_ATTACHED(QQStyleKitAttached)
+
+public:
+ QQStyleKit(QObject *parent = nullptr);
+ static QQStyleKitAttached *qmlAttachedProperties(QObject *obj = nullptr);
+
+private:
+ Q_DISABLE_COPY(QQStyleKit)
+};
+
+class QQStyleKitAttached : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQStyleKitStyle *style READ style WRITE setStyle NOTIFY styleChanged FINAL)
+ Q_PROPERTY(QString styleUrl READ styleUrl WRITE setStyleUrl NOTIFY styleUrlChanged FINAL)
+ Q_PROPERTY(bool transitionsEnabled READ transitionsEnabled WRITE setTransitionsEnabled NOTIFY transitionsEnabledChanged FINAL)
+ Q_PROPERTY(QQStyleKitDebug *debug READ debug FINAL)
+
+public:
+ QQStyleKitAttached(QObject *parent);
+ ~QQStyleKitAttached();
+
+ QQStyleKitStyle *style() const;
+ void setStyle(QQStyleKitStyle *style);
+
+ QString styleUrl() const;
+ void setStyleUrl(const QString &styleUrl);
+
+ bool transitionsEnabled() const;
+ void setTransitionsEnabled(bool enabled);
+
+ QQStyleKitDebug *debug() const;
+
+ Q_INVOKABLE bool styleLoaded() const;
+
+signals:
+ void styleChanged();
+ void styleUrlChanged();
+ void transitionsEnabledChanged();
+
+private:
+ bool m_ownsStyle = false;
+ QString m_styleUrl;
+ QPointer<QQmlEngine> m_engine;
+ QPointer<QQStyleKitStyle> m_style;
+ QQStyleKitDebug m_debug;
+
+ static QPointer<QQStyleKitAttached> s_instance;
+ static bool s_transitionsEnabled;
+
+ friend class QQStyleKit;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKIT_P_H
diff --git a/src/labs/stylekit/qqstylekitcontrol.cpp b/src/labs/stylekit/qqstylekitcontrol.cpp
new file mode 100644
index 0000000000..41f4f33028
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrol.cpp
@@ -0,0 +1,59 @@
+// 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
+
+#include "qqstylekitcontrol_p.h"
+#include "qqstylekitpropertyresolver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitControl::QQStyleKitControl(QObject *parent)
+ : QQStyleKitControlState(parent)
+{
+}
+
+QQmlListProperty<QQStyleKitVariation> QQStyleKitControl::variations()
+{
+ const bool isWrite = subclass() == QQSK::Subclass::QQStyleKitState;
+ if (isWrite) {
+ if (!QQStyleKitPropertyResolver::hasLocalStyleProperty(this, QQSK::Property::Variations)) {
+ /* We need to handle both the setter and the getter in the getter, since QML doesn't
+ * use a separate setter for QQmlListProperty. This means that we need to initialize
+ * the storage with an empty QList, since we need to be prepared for the QML engine
+ * inserting elements into it behind our back. A negative side-effect of this logic
+ * is that a simple read of the property for a QQStyleKitState will also accidentally
+ * populate the local storage of that QQStylKitState, and thereby affect propagation.
+ * Note: since Variations is not a part of QQStyleKitProperties, it's not possible,
+ * and there is also no point, in emitting a changed signal globally, since a
+ * QQuickStyleKitReader inherits QQStyleKitProperties and not QQStyleKitControl,
+ * and as such, doesn't have a 'variations' property. */
+ auto *newList = new QList<QQStyleKitVariation *>();
+ setStyleProperty(QQSK::Property::Variations, newList);
+ }
+ }
+
+ /* Read the property, taking propagation into account. Note that since QQmlListProperty takes
+ * a pointer to a QList as argument, we need to store the list as a pointer as well */
+ const QVariant variant = QQStyleKitPropertyResolver::readStyleProperty(this, QQSK::Property::Variations);
+ if (!variant.isValid()) {
+ // Return a read-only list. Trying to change this list from the app has no effect.
+ static auto *emptyList = new QList<QQStyleKitVariation *>();
+ return QQmlListProperty<QQStyleKitVariation>(this, emptyList);
+ }
+
+ const auto value = qvariant_cast<QList<QQStyleKitVariation *> *>(variant);
+ return QQmlListProperty<QQStyleKitVariation>(this, value);
+}
+
+QVariant QQStyleKitControl::readStyleProperty(PropertyStorageId key) const
+{
+ return m_storage.value(key);
+}
+
+void QQStyleKitControl::writeStyleProperty(PropertyStorageId key, const QVariant &value)
+{
+ m_storage.insert(key, value);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitcontrol_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitcontrol_p.h b/src/labs/stylekit/qqstylekitcontrol_p.h
new file mode 100644
index 0000000000..307046a198
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrol_p.h
@@ -0,0 +1,55 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITCONTROL_P_H
+#define QQSTYLEKITCONTROL_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 <QtQml/QtQml>
+#include "qqstylekitcontrolstate_p.h"
+#include "qqstylekitstorage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitVariation;
+
+class QQStyleKitControl : public QQStyleKitControlState
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlListProperty<QQStyleKitVariation> variations READ variations FINAL)
+ QML_NAMED_ELEMENT(StyleKitControl)
+
+public:
+ QQStyleKitControl(QObject *parent = nullptr);
+
+ QQmlListProperty<QQStyleKitVariation> variations();
+
+private:
+ QVariant readStyleProperty(PropertyStorageId key) const;
+ void writeStyleProperty(PropertyStorageId key, const QVariant &value);
+
+private:
+ Q_DISABLE_COPY(QQStyleKitControl)
+
+ QList<QQStyleKitVariation *> m_variations;
+ mutable QQStyleKitPropertyStorage m_storage;
+ QQSK::State m_writtenStates = QQSK::StateFlag::NoState;
+
+ friend class QQStyleKitPropertyResolver;
+ friend class QQStyleKitControls;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITCONTROL_P_H
diff --git a/src/labs/stylekit/qqstylekitcontrolproperties.cpp b/src/labs/stylekit/qqstylekitcontrolproperties.cpp
new file mode 100644
index 0000000000..9b11b560e5
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrolproperties.cpp
@@ -0,0 +1,1014 @@
+// 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
+
+#include "qqstylekitcontrol_p.h"
+#include "qqstylekitstyle_p.h"
+#include "qqstylekittheme_p.h"
+#include "qqstylekitcontrolproperties_p.h"
+#include "qqstylekitpropertyresolver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+// ************* QQStyleKitPropertyGroup ****************
+
+QQStyleKitPropertyGroup::QQStyleKitPropertyGroup(QQSK::PropertyGroup group, QObject *parent)
+ : QObject(parent)
+ , m_group(group)
+{
+}
+
+/* This macro will check if the caller has the same group path as \a GROUP_PATH.
+ * This is needed since a QQSK::Property (e.g Color) can sometimes be a
+ * property in several different subclasses of QQStyleKitPropertyGroup.
+ * For example, both control.background.color and control.indicator.color has a
+ * color property. But the group path differs, so they are in reality two completely
+ * different properties. And in that case, when the former changes value, we want to
+ * emit changes globally only to that property, and not the latter.
+ * The caller of this macro will therefore need to go through all the usages of its
+ * subclass in the API, to figure out which group itself is an instance of. For the
+ * one that is a match, the macro will go through all readers and emit the same
+ * signal for them. */
+#define CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(CONTROL_PROPERTIES, GROUP_PATH) \
+if (this == CONTROL_PROPERTIES -> GROUP_PATH ) { \
+ for (QQStyleKitReader *reader : QQStyleKitReader::s_allReaders) { \
+ reader->clearLocalStorage(); \
+ ((reader-> GROUP_PATH ->*changedSignals)(), ...); \
+ } \
+ return; \
+}
+
+template<typename SUBCLASS>
+void QQStyleKitPropertyGroup::handleStylePropertyChanged(void (SUBCLASS::*changedSignal)()) {
+ handleStylePropertiesChanged<SUBCLASS>(changedSignal);
+}
+
+template <typename SUBCLASS, typename... CHANGED_SIGNALS>
+void QQStyleKitPropertyGroup::handleStylePropertiesChanged(CHANGED_SIGNALS... changedSignals)
+{
+ /* This function will check which subclass of QQStyleKitProperties this
+ * group is (nested) inside. Based on that, it will decide if the signals
+ * should be emitted locally or not, and if the changed properties affects
+ * all existing QQStyleKitReaders, and therefore will need to be
+ * emitted 'globally'. Note that it only makes sense to call this function
+ * for changed properties that are available from a QQStyleKitReader.
+ * Properities only available from e.g QQStyleKitControl (such as
+ * variations), are anyway not readable from a QQStyleKitReader. */
+ static_assert(std::is_base_of<QQStyleKitPropertyGroup, SUBCLASS>::value,
+ "SUBCLASS must inherit QQStyleKitPropertyGroup");
+
+ auto *group = static_cast<SUBCLASS *>(this);
+ const QQStyleKitControlProperties *root = controlProperties();
+ const QQSK::Subclass objectWrittenTo = root->subclass();
+
+ if (objectWrittenTo == QQSK::Subclass::QQStyleKitState) {
+ ((group->*changedSignals)(), ...);
+ if (shouldEmitGlobally())
+ group->emitGlobally(changedSignals...);
+ return;
+ }
+
+ if (objectWrittenTo == QQSK::Subclass::QQStyleKitReader) {
+ /* Unless the StyleReader has told us not to emit any signals (because it's only
+ * syncing it's own local storage with old values before starting a transition), we
+ * emit the signal like normal. This will cause the control to repaint (perhaps
+ * using a transition). */
+ if (shouldEmitLocally())
+ ((group->*changedSignals)(), ...);
+ return;
+ }
+
+ Q_UNREACHABLE();
+}
+
+const QQStyleKitControlProperties *QQStyleKitPropertyGroup::controlProperties() const
+{
+ const QQStyleKitPropertyGroup *group = this;
+ while (!group->isControlProperties()) {
+ group = static_cast<QQStyleKitPropertyGroup *>(group->parent());
+ Q_ASSERT(group);
+ }
+ return group->asControlProperties();
+}
+
+std::tuple<
+ const QQStyleKitControlProperties *,
+ const QQSK::PropertyGroup,
+ const QQSK::PathFlags>
+QQStyleKitPropertyGroup::inspectGroupPath() const
+{
+ /* The path of a property can sometimes contain groups that are hints to the property
+ * resolver. E.g in the path 'QQStyleKitReader.global.handle.first.padding', 'global'
+ * hints that the property should be read directly from the style, circumventing the local
+ * cache that stores interpolated transition values. 'first' hints that the group is inside
+ * a sub type, which will affect how the style reader handles propagation. */
+ QQSK::PathFlags pathFlags = QQSK::PathFlag::NoFlag;
+ QQSK::PropertyGroup subType = QQSK::PropertyGroup::NoGroup;
+
+ const QQStyleKitPropertyGroup *group = this;
+ while (!group->isControlProperties()) {
+ if (group->m_group == QQSK::PropertyGroup::DelegateSubType1)
+ subType = QQSK::PropertyGroup::DelegateSubType1;
+ else if (group->m_group == QQSK::PropertyGroup::DelegateSubType2)
+ subType = QQSK::PropertyGroup::DelegateSubType2;
+ else if (group->m_group == QQSK::PropertyGroup::globalFlag)
+ pathFlags |= QQSK::PathFlag::StyleDirect;
+
+ group = group->parentGroup();
+ Q_ASSERT(group);
+ }
+
+ const auto *controlProperties = group->asControlProperties();
+ return std::make_tuple(controlProperties, subType, pathFlags);
+}
+
+const QQStyleKitControlProperties *QQStyleKitPropertyGroup::asControlProperties() const
+{
+ Q_ASSERT(isControlProperties());
+ return static_cast<const QQStyleKitControlProperties *>(this);
+}
+
+void QQStyleKitPropertyGroup::emitChangedForAllStylePropertiesRecursive()
+{
+ /* This function will emit changed signals for all style properties in the
+ * StyleKit API (for a single QQStyleKitReader), which is needed after
+ * doing a style-, or theme change. */
+ const int startIndex = QQStyleKitPropertyGroup::staticMetaObject.propertyOffset();
+ const QMetaObject* meta = metaObject();
+ for (int i = startIndex; i < meta->propertyCount(); ++i) {
+ const QMetaProperty prop = meta->property(i);
+ const QMetaObject* metaObject = QMetaType::fromName(prop.typeName()).metaObject();
+ if (metaObject) {
+ if (metaObject->inherits(&QQStyleKitDelegateProperties::staticMetaObject)) {
+ /* Skip recursing into QQStyleKitDelegateProperties, because those are lazy
+ * created when read, and reading them from here would accidentally
+ * create them. */
+ continue;
+ }
+ if (metaObject->inherits(&QQStyleKitPropertyGroup::staticMetaObject)) {
+ // The property is of type QQStyleKitPropertyGroup, so recurse into it
+ QObject *childObj = qvariant_cast<QObject *>(property(prop.name()));
+ if (auto *child = qobject_cast<QQStyleKitPropertyGroup *>(childObj))
+ child->emitChangedForAllStylePropertiesRecursive();
+ continue;
+ }
+ }
+
+ // Emit the changed signal for the property
+ Q_ASSERT(prop.hasNotifySignal());
+ QMetaMethod notify = prop.notifySignal();
+ notify.invoke(this, Qt::DirectConnection);
+ }
+}
+
+bool QQStyleKitPropertyGroup::shouldEmitLocally()
+{
+ const QQStyleKitControlProperties *root = controlProperties();
+ return !root->asQQStyleKitReader()->dontEmitChangedSignals();
+}
+
+bool QQStyleKitPropertyGroup::shouldEmitGlobally()
+{
+ const QQStyleKitControlProperties *root = controlProperties();
+ QQStyleKitStyle *parentStyle = root->style();
+ if (!parentStyle)
+ return false;
+
+ if (parentStyle->loaded() && !parentStyle->m_isUpdatingPalette) {
+ /* When a property has changed in the 'global' QQStyleKitStyle itself, it can
+ * potentially affect all control instances. We therefore need to go through all
+ * QQStyleKitReaders and inform that their own local property that matches the
+ * 'global' property needs to be re-read. We emit the signals directly, omitting any
+ * applied transitions in the QQStyleKitReaders, to optimize for speed. The exception
+ * is if we're just updating the palette in the Style to match the palette in the current
+ * control / QQStyleKitReader. Such a change will only affect a single control. */
+ return parentStyle == QQStyleKitStyle::current();
+ }
+ return false;
+}
+
+// ************* QQStyleKitImageProperties ****************
+
+QQStyleKitImageProperties::QQStyleKitImageProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitPropertyGroup(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitImageProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ // Go through all instances of QQStyleKitImageProperties
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, background()->image());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, handle()->image());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->image());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->foreground()->image());
+}
+
+QUrl QQStyleKitImageProperties::source() const
+{
+ return styleProperty<QUrl>(QQSK::Property::Source);
+}
+
+void QQStyleKitImageProperties::setSource(const QUrl &source)
+{
+ if (setStyleProperty(QQSK::Property::Source, source))
+ handleStylePropertyChanged(&QQStyleKitImageProperties::sourceChanged);
+}
+
+QColor QQStyleKitImageProperties::color() const
+{
+ return styleProperty<QColor>(QQSK::Property::Color);
+}
+
+void QQStyleKitImageProperties::setColor(const QColor &color)
+{
+ if (setStyleProperty(QQSK::Property::Color, color))
+ handleStylePropertyChanged(&QQStyleKitImageProperties::colorChanged);
+}
+
+QQuickImage::FillMode QQStyleKitImageProperties::fillMode() const
+{
+ return styleProperty<QQuickImage::FillMode>(QQSK::Property::FillMode);
+}
+
+void QQStyleKitImageProperties::setFillMode(QQuickImage::FillMode fillMode)
+{
+ if (setStyleProperty(QQSK::Property::FillMode, fillMode))
+ handleStylePropertyChanged(&QQStyleKitImageProperties::fillModeChanged);
+}
+
+// ************* QQStyleKitBorderProperties ****************
+
+QQStyleKitBorderProperties::QQStyleKitBorderProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitPropertyGroup(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitBorderProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ // Go through all instances of QQStyleKitBorderProperties
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, background()->border());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, handle()->border());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->border());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->foreground()->border());
+}
+
+qreal QQStyleKitBorderProperties::width() const
+{
+ return styleProperty<qreal>(QQSK::Property::Width);
+}
+
+void QQStyleKitBorderProperties::setWidth(qreal width)
+{
+ if (setStyleProperty(QQSK::Property::Width, width))
+ handleStylePropertyChanged(&QQStyleKitBorderProperties::widthChanged);
+}
+
+QColor QQStyleKitBorderProperties::color() const
+{
+ return styleProperty<QColor>(QQSK::Property::Color, Qt::transparent);
+}
+
+void QQStyleKitBorderProperties::setColor(const QColor &color)
+{
+ if (setStyleProperty(QQSK::Property::Color, color))
+ handleStylePropertyChanged(&QQStyleKitBorderProperties::colorChanged);
+}
+
+// ************* QQStyleKitShadowProperties ****************
+
+QQStyleKitShadowProperties::QQStyleKitShadowProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitPropertyGroup(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitShadowProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ // Go through all instances of QQStyleKitShadowProperties
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, background()->shadow());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, handle()->shadow());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->shadow());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->foreground()->shadow());
+}
+
+QColor QQStyleKitShadowProperties::color() const
+{
+ return styleProperty<QColor>(QQSK::Property::Color, Qt::transparent);
+}
+
+void QQStyleKitShadowProperties::setColor(QColor color)
+{
+ if (setStyleProperty(QQSK::Property::Color, color))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::colorChanged);
+}
+
+qreal QQStyleKitShadowProperties::opacity() const
+{
+ return styleProperty<qreal>(QQSK::Property::Opacity, 1.0);
+}
+
+void QQStyleKitShadowProperties::setOpacity(qreal opacity)
+{
+ if (setStyleProperty(QQSK::Property::Opacity, opacity))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::opacityChanged);
+}
+
+qreal QQStyleKitShadowProperties::scale() const
+{
+ return styleProperty<qreal>(QQSK::Property::Scale, 1.0);
+}
+
+void QQStyleKitShadowProperties::setScale(qreal scale)
+{
+ if (setStyleProperty(QQSK::Property::Scale, scale))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::scaleChanged);
+}
+
+qreal QQStyleKitShadowProperties::verticalOffset() const
+{
+ return styleProperty<qreal>(QQSK::Property::VOffset);
+}
+
+void QQStyleKitShadowProperties::setVerticalOffset(qreal verticalOffset)
+{
+ if (setStyleProperty(QQSK::Property::VOffset, verticalOffset))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::verticalOffsetChanged);
+}
+
+qreal QQStyleKitShadowProperties::horizontalOffset() const
+{
+ return styleProperty<qreal>(QQSK::Property::HOffset);
+}
+
+void QQStyleKitShadowProperties::setHorizontalOffset(qreal horizontalOffset)
+{
+ if (setStyleProperty(QQSK::Property::HOffset, horizontalOffset))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::horizontalOffsetChanged);
+}
+
+qreal QQStyleKitShadowProperties::blur() const
+{
+ return styleProperty<qreal>(QQSK::Property::Blur, 10.0);
+}
+
+void QQStyleKitShadowProperties::setBlur(qreal blur)
+{
+ if (setStyleProperty(QQSK::Property::Blur, blur))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::blurChanged);
+}
+
+bool QQStyleKitShadowProperties::visible() const
+{
+ return styleProperty<bool>(QQSK::Property::Visible, true);
+}
+
+void QQStyleKitShadowProperties::setVisible(bool visible)
+{
+ if (setStyleProperty(QQSK::Property::Visible, visible))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::visibleChanged);
+}
+
+QQmlComponent *QQStyleKitShadowProperties::delegate() const
+{
+ return styleProperty<QQmlComponent *>(QQSK::Property::Delegate);
+}
+
+void QQStyleKitShadowProperties::setDelegate(QQmlComponent *delegate)
+{
+ if (setStyleProperty(QQSK::Property::Delegate, delegate))
+ handleStylePropertyChanged(&QQStyleKitShadowProperties::delegateChanged);
+}
+
+// ************* QQStyleKitDelegateProperties ****************
+
+QQStyleKitDelegateProperties::QQStyleKitDelegateProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitPropertyGroup(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitDelegateProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ // Go through all instances of QQStyleKitDelegateProperties
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, background());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, handle());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->foreground());
+}
+
+qreal QQStyleKitDelegateProperties::radius() const
+{
+ return styleProperty<qreal>(QQSK::Property::Radius);
+}
+
+void QQStyleKitDelegateProperties::setRadius(qreal radius)
+{
+ if (setStyleProperty(QQSK::Property::Radius, radius))
+ handleStylePropertiesChanged<QQStyleKitDelegateProperties>(
+ &QQStyleKitDelegateProperties::radiusChanged,
+ &QQStyleKitDelegateProperties::topLeftRadiusChanged,
+ &QQStyleKitDelegateProperties::topRightRadiusChanged,
+ &QQStyleKitDelegateProperties::bottomLeftRadiusChanged,
+ &QQStyleKitDelegateProperties::bottomRightRadiusChanged);
+}
+
+qreal QQStyleKitDelegateProperties::topLeftRadius() const
+{
+ return styleProperty<qreal>(QQSK::Property::TopLeftRadius, QQSK::Property::Radius);
+}
+
+void QQStyleKitDelegateProperties::setTopLeftRadius(qreal radius)
+{
+ if (setStyleProperty(QQSK::Property::TopLeftRadius, radius))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::topLeftRadiusChanged);
+}
+
+qreal QQStyleKitDelegateProperties::topRightRadius() const
+{
+ return styleProperty<qreal>(QQSK::Property::TopRightRadius, QQSK::Property::Radius);
+}
+
+void QQStyleKitDelegateProperties::setTopRightRadius(qreal radius)
+{
+ if (setStyleProperty(QQSK::Property::TopRightRadius, radius))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::topRightRadiusChanged);
+}
+
+qreal QQStyleKitDelegateProperties::bottomLeftRadius() const
+{
+ return styleProperty<qreal>(QQSK::Property::BottomLeftRadius, QQSK::Property::Radius);
+}
+
+void QQStyleKitDelegateProperties::setBottomLeftRadius(qreal radius)
+{
+ if (setStyleProperty(QQSK::Property::BottomLeftRadius, radius))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::bottomLeftRadiusChanged);
+}
+
+qreal QQStyleKitDelegateProperties::bottomRightRadius() const
+{
+ return styleProperty<qreal>(QQSK::Property::BottomRightRadius, QQSK::Property::Radius);
+}
+
+void QQStyleKitDelegateProperties::setBottomRightRadius(qreal radius)
+{
+ if (setStyleProperty(QQSK::Property::BottomRightRadius, radius))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::bottomRightRadiusChanged);
+}
+
+qreal QQStyleKitDelegateProperties::scale() const
+{
+ return styleProperty<qreal>(QQSK::Property::Scale, 1.0);
+}
+
+void QQStyleKitDelegateProperties::setScale(qreal scale)
+{
+ if (setStyleProperty(QQSK::Property::Scale, scale))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::scaleChanged);
+}
+
+qreal QQStyleKitDelegateProperties::rotation() const
+{
+ return styleProperty<qreal>(QQSK::Property::Rotation);
+}
+
+void QQStyleKitDelegateProperties::setRotation(qreal rotation)
+{
+ if (setStyleProperty(QQSK::Property::Rotation, rotation))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::rotationChanged);
+}
+
+qreal QQStyleKitDelegateProperties::implicitWidth() const
+{
+ return styleProperty<qreal>(QQSK::Property::ImplicitWidth);
+}
+
+void QQStyleKitDelegateProperties::setImplicitWidth(qreal width)
+{
+ if (setStyleProperty(QQSK::Property::ImplicitWidth, width))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::implicitWidthChanged);
+}
+
+qreal QQStyleKitDelegateProperties::implicitHeight() const
+{
+ return styleProperty<qreal>(QQSK::Property::ImplicitHeight);
+}
+
+void QQStyleKitDelegateProperties::setImplicitHeight(qreal height)
+{
+ if (setStyleProperty(QQSK::Property::ImplicitHeight, height))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::implicitHeightChanged);
+}
+
+qreal QQStyleKitDelegateProperties::minimumWidth() const
+{
+ return styleProperty<qreal>(QQSK::Property::MinimumWidth);
+}
+
+void QQStyleKitDelegateProperties::setMinimumWidth(qreal width)
+{
+ if (setStyleProperty(QQSK::Property::MinimumWidth, width))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::minimumWidthChanged);
+}
+
+qreal QQStyleKitDelegateProperties::margins() const
+{
+ return styleProperty<qreal>(QQSK::Property::Margins);
+}
+
+void QQStyleKitDelegateProperties::setMargins(qreal margins)
+{
+ if (setStyleProperty(QQSK::Property::Margins, margins))
+ handleStylePropertiesChanged<QQStyleKitDelegateProperties>(
+ &QQStyleKitDelegateProperties::marginsChanged,
+ &QQStyleKitDelegateProperties::leftMarginChanged,
+ &QQStyleKitDelegateProperties::rightMarginChanged,
+ &QQStyleKitDelegateProperties::topMarginChanged,
+ &QQStyleKitDelegateProperties::bottomMarginChanged);
+}
+
+qreal QQStyleKitDelegateProperties::leftMargin() const
+{
+ return styleProperty<qreal>(QQSK::Property::LeftMargin, QQSK::Property::Margins);
+}
+
+void QQStyleKitDelegateProperties::setLeftMargin(qreal margin)
+{
+ if (setStyleProperty(QQSK::Property::LeftMargin, margin))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::leftMarginChanged);
+}
+
+qreal QQStyleKitDelegateProperties::rightMargin() const
+{
+ return styleProperty<qreal>(QQSK::Property::RightMargin, QQSK::Property::Margins);
+}
+
+void QQStyleKitDelegateProperties::setRightMargin(qreal margin)
+{
+ if (setStyleProperty(QQSK::Property::RightMargin, margin))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::rightMarginChanged);
+}
+
+qreal QQStyleKitDelegateProperties::topMargin() const
+{
+ return styleProperty<qreal>(QQSK::Property::TopMargin, QQSK::Property::Margins);
+}
+
+void QQStyleKitDelegateProperties::setTopMargin(qreal margin)
+{
+ if (setStyleProperty(QQSK::Property::TopMargin, margin))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::topMarginChanged);
+}
+
+qreal QQStyleKitDelegateProperties::bottomMargin() const
+{
+ return styleProperty<qreal>(QQSK::Property::BottomMargin, QQSK::Property::Margins);
+}
+
+void QQStyleKitDelegateProperties::setBottomMargin(qreal margin)
+{
+ if (setStyleProperty(QQSK::Property::BottomMargin, margin))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::bottomMarginChanged);
+}
+
+Qt::Alignment QQStyleKitDelegateProperties::alignment() const
+{
+ return styleProperty<Qt::Alignment>(QQSK::Property::Alignment, Qt::AlignLeft | Qt::AlignVCenter);
+}
+
+void QQStyleKitDelegateProperties::setAlignment(Qt::Alignment alignment)
+{
+ if (setStyleProperty(QQSK::Property::Alignment, alignment))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::alignmentChanged);
+}
+
+qreal QQStyleKitDelegateProperties::opacity() const
+{
+ return styleProperty<qreal>(QQSK::Property::Opacity, 1.0);
+}
+
+void QQStyleKitDelegateProperties::setOpacity(qreal opacity)
+{
+ if (setStyleProperty(QQSK::Property::Opacity, opacity))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::opacityChanged);
+}
+
+QColor QQStyleKitDelegateProperties::color() const
+{
+ return styleProperty<QColor>(QQSK::Property::Color, Qt::transparent);
+}
+
+void QQStyleKitDelegateProperties::setColor(const QColor &color)
+{
+ if (setStyleProperty(QQSK::Property::Color, color))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::colorChanged);
+}
+
+bool QQStyleKitDelegateProperties::visible() const
+{
+ return styleProperty<bool>(QQSK::Property::Visible, true);
+}
+
+void QQStyleKitDelegateProperties::setVisible(bool visible)
+{
+ if (setStyleProperty(QQSK::Property::Visible, visible))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::visibleChanged);
+}
+
+bool QQStyleKitDelegateProperties::clip() const
+{
+ return styleProperty<bool>(QQSK::Property::Clip, false);
+}
+
+void QQStyleKitDelegateProperties::setClip(bool clip)
+{
+ if (setStyleProperty(QQSK::Property::Clip, clip))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::clipChanged);
+}
+
+QQuickGradient *QQStyleKitDelegateProperties::gradient() const
+{
+ return styleProperty<QQuickGradient *>(QQSK::Property::Gradient);
+}
+
+void QQStyleKitDelegateProperties::setGradient(QQuickGradient *gradient)
+{
+ if (setStyleProperty(QQSK::Property::Gradient, gradient))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::gradientChanged);
+}
+
+QObject *QQStyleKitDelegateProperties::data() const
+{
+ return styleProperty<QObject *>(QQSK::Property::Data);
+}
+
+void QQStyleKitDelegateProperties::setData(QObject *data)
+{
+ if (setStyleProperty(QQSK::Property::Data, data))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::dataChanged);
+}
+
+QQmlComponent *QQStyleKitDelegateProperties::delegate() const
+{
+ return styleProperty<QQmlComponent *>(QQSK::Property::Delegate);
+}
+
+void QQStyleKitDelegateProperties::setDelegate(QQmlComponent *delegate)
+{
+ if (setStyleProperty(QQSK::Property::Delegate, delegate))
+ handleStylePropertyChanged(&QQStyleKitDelegateProperties::delegateChanged);
+}
+
+QQStyleKitBorderProperties *QQStyleKitDelegateProperties::border() const
+{
+ return lazyCreateGroup(m_border, QQSK::PropertyGroup::Border);
+}
+
+QQStyleKitShadowProperties *QQStyleKitDelegateProperties::shadow() const
+{
+ return lazyCreateGroup(m_shadow, QQSK::PropertyGroup::Shadow);
+}
+
+QQStyleKitImageProperties *QQStyleKitDelegateProperties::image() const
+{
+ return lazyCreateGroup(m_image, QQSK::PropertyGroup::Image);
+}
+
+// ************* QQStyleKitHandleProperties ****************
+
+QQStyleKitHandleProperties::QQStyleKitHandleProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitDelegateProperties(group, parent)
+{
+}
+
+QQStyleKitDelegateProperties *QQStyleKitHandleProperties::first() const
+{
+ return lazyCreateGroup(m_first, QQSK::PropertyGroup::DelegateSubType1);
+}
+
+QQStyleKitDelegateProperties *QQStyleKitHandleProperties::second() const
+{
+ return lazyCreateGroup(m_second, QQSK::PropertyGroup::DelegateSubType2);
+}
+
+// ************* QQStyleKitIndicatorProperties ****************
+
+QQStyleKitIndicatorProperties::QQStyleKitIndicatorProperties(
+ QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitDelegateProperties(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitIndicatorProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ // Go through all instances of QQStyleKitIndicatorProperties
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->up());
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator()->down());
+}
+
+QQStyleKitDelegateProperties *QQStyleKitIndicatorProperties::foreground() const
+{
+ return lazyCreateGroup(m_foreground, QQSK::PropertyGroup::Foreground);
+}
+
+// ************* QQStyleKitIndicatorWithSubTypes ****************
+
+QQStyleKitIndicatorWithSubTypes::QQStyleKitIndicatorWithSubTypes(
+ QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitDelegateProperties(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitIndicatorWithSubTypes::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ // Go through all instances of QQStyleKitIndicatorWithSubTypes
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, indicator());
+}
+
+QQStyleKitDelegateProperties *QQStyleKitIndicatorWithSubTypes::foreground() const
+{
+ return lazyCreateGroup(m_foreground, QQSK::PropertyGroup::Foreground);
+}
+
+QQStyleKitIndicatorProperties *QQStyleKitIndicatorWithSubTypes::up() const
+{
+ return lazyCreateGroup(m_up, QQSK::PropertyGroup::DelegateSubType1);
+}
+
+QQStyleKitIndicatorProperties *QQStyleKitIndicatorWithSubTypes::down() const
+{
+ return lazyCreateGroup(m_down, QQSK::PropertyGroup::DelegateSubType2);
+}
+
+// ************* QQStyleKitTextProperties ****************
+QQStyleKitTextProperties::QQStyleKitTextProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent)
+ : QQStyleKitPropertyGroup(group, parent)
+{
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitTextProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ const QQStyleKitControlProperties *cp = controlProperties();
+ CONDITIONALLY_EMIT_SIGNALS_GLOBALLY_FOR(cp, text());
+}
+
+QColor QQStyleKitTextProperties::color() const
+{
+ return styleProperty<QColor>(QQSK::Property::Color);
+}
+
+void QQStyleKitTextProperties::setColor(const QColor &color)
+{
+ if (setStyleProperty(QQSK::Property::Color, color))
+ handleStylePropertyChanged(&QQStyleKitTextProperties::colorChanged);
+}
+
+Qt::Alignment QQStyleKitTextProperties::alignment() const
+{
+ return styleProperty<Qt::Alignment>(QQSK::Property::Alignment);
+}
+
+void QQStyleKitTextProperties::setAlignment(Qt::Alignment alignment)
+{
+ if (setStyleProperty(QQSK::Property::Alignment, alignment))
+ handleStylePropertyChanged(&QQStyleKitTextProperties::alignmentChanged);
+}
+
+// ************* QQStyleKitControlProperties ****************
+
+QQStyleKitControlProperties::QQStyleKitControlProperties(QQSK::PropertyGroup group, QObject *parent)
+ : QQStyleKitPropertyGroup(group, parent)
+{
+}
+
+QQStyleKitStyle *QQStyleKitControlProperties::style() const
+{
+ if (subclass() == QQSK::Subclass::QQStyleKitState) {
+ /* A QQStyleKitControlState (and its subclasses) should always be a (grand)child of a
+ * QQStyleKitStyle. And it belongs to that style, even it that style is not the
+ * currently active application style. This is opposed to a QQStyleKitReader,
+ * that normally belongs / communicates with the currently active style.
+ * NOTE: a style can also be a fallback style for another style (which can be recursive,
+ * meaning that a fallback style can also have its own fallback style, and so on). But
+ * this function will return the nearest style, and not the root style */
+ QObject *obj = parent();
+ while (obj && !obj->metaObject()->inherits(&QQStyleKitStyle::staticMetaObject))
+ obj = obj->parent();
+ return obj ? static_cast<QQStyleKitStyle *>(obj) : nullptr;
+ }
+
+ /* A style reader belongs to the currently active application style. We could in theory
+ * support being able to point a QQStyleKitReader to any style, which would basically
+ * mean that you could mix controls from several styles inside the same application. But
+ * there is currently no API (or use-case?) in Controls that lets you to do that, so its
+ * disabled for now. */
+ return QQStyleKitStyle::current();
+}
+
+QQSK::Subclass QQStyleKitControlProperties::subclass() const
+{
+ /* QQStyleKitControlProperties is subclassed by several different classes in this
+ * framework. As such, it's basically just an interface because it only declares the
+ * different properties that can be read or written to in a QQStyleKitStyle, such as
+ * hovered.background.color or pressed.indicator.foreground.color. It says nothing
+ * about how those properties are stored, instead that is up to each individual
+ * subclass to decide */
+ if (metaObject()->inherits(&QQStyleKitReader::staticMetaObject))
+ return QQSK::Subclass::QQStyleKitReader;
+ if (metaObject()->inherits(&QQStyleKitControlState::staticMetaObject))
+ return QQSK::Subclass::QQStyleKitState;
+ Q_UNREACHABLE();
+}
+
+QQStyleKitReader *QQStyleKitControlProperties::asQQStyleKitReader() const
+{
+ Q_ASSERT(subclass() == QQSK::Subclass::QQStyleKitReader);
+ return static_cast<QQStyleKitReader *>(const_cast<QQStyleKitControlProperties *>(this));
+}
+
+QQStyleKitControlState *QQStyleKitControlProperties::asQQStyleKitState() const
+{
+ Q_ASSERT(subclass() == QQSK::Subclass::QQStyleKitState);
+ Q_ASSERT(metaObject()->inherits(&QQStyleKitControlState::staticMetaObject));
+ return static_cast<QQStyleKitControlState *>(const_cast<QQStyleKitControlProperties *>(this));
+}
+
+QQStyleKitControl *QQStyleKitControlProperties::asQQStyleKitControl() const
+{
+ Q_ASSERT(subclass() == QQSK::Subclass::QQStyleKitState);
+ Q_ASSERT(metaObject()->inherits(&QQStyleKitControl::staticMetaObject));
+ return static_cast<QQStyleKitControl *>(const_cast<QQStyleKitControlProperties *>(this));
+}
+
+void QQStyleKitControlProperties::forEachUsedDelegate(
+ std::function<void (QQStyleKitDelegateProperties *, QQSK::Delegate, const QString &)> f)
+{
+ // If adding more delegates here, remember to keep StyleKitAnimation.qml in sync
+ if (m_background)
+ f(m_background, QQSK::Delegate::Background, "background"_L1);
+
+ if (m_indicator) {
+ f(m_indicator, QQSK::Delegate::Indicator, "indicator"_L1);
+ if (m_indicator->m_foreground)
+ f(m_indicator->m_foreground, QQSK::Delegate::IndicatorForeground, "indicator.foreground"_L1);
+ if (m_indicator->m_up) {
+ f(m_indicator->m_up, QQSK::Delegate::IndicatorUp, "indicator.up"_L1);
+ if (m_indicator->m_up->m_foreground)
+ f(m_indicator->m_up->m_foreground, QQSK::Delegate::IndicatorUpForeground, "indicator.up.foreground"_L1);
+ }
+ if (m_indicator->m_down) {
+ f(m_indicator->m_down, QQSK::Delegate::IndicatorDown, "indicator.down"_L1);
+ if (m_indicator->m_down->m_foreground)
+ f(m_indicator->m_down->m_foreground, QQSK::Delegate::IndicatorDownForeground, "indicator.down.foreground"_L1);
+ }
+ }
+
+ if (m_handle) {
+ f(m_handle, QQSK::Delegate::Handle, "handle"_L1);
+ if (m_handle->m_first)
+ f(m_handle->m_first, QQSK::Delegate::HandleFirst, "handle.first"_L1);
+ if (m_handle->m_second)
+ f(m_handle->m_second, QQSK::Delegate::HandleSecond, "handle.second"_L1);
+ }
+}
+
+void QQStyleKitControlProperties::emitChangedForAllStyleProperties()
+{
+ /* This brute-force function will emit update signals for _all_ style properties
+ * in the QQStyleKitStyle API. Doing so is typically needed after a style-, or theme
+ * change, as we don't know which properties are affected by such a big change. */
+ emit leftPaddingChanged();
+ emit rightPaddingChanged();
+ emit topPaddingChanged();
+ emit bottomPaddingChanged();
+ emit spacingChanged();
+ emit transitionChanged();
+ emit textChanged();
+
+ forEachUsedDelegate([](QQStyleKitDelegateProperties *delegate, QQSK::Delegate, const QString &){
+ delegate->emitChangedForAllStylePropertiesRecursive();
+ });
+}
+
+template <typename... CHANGED_SIGNALS>
+void QQStyleKitControlProperties::emitGlobally(CHANGED_SIGNALS... changedSignals) const
+{
+ for (QQStyleKitReader *reader : QQStyleKitReader::s_allReaders)
+ ((reader->*changedSignals)(), ...);
+}
+
+qreal QQStyleKitControlProperties::spacing() const
+{
+ return styleProperty<qreal>(QQSK::Property::Spacing);
+}
+
+void QQStyleKitControlProperties::setSpacing(qreal spacing)
+{
+ if (setStyleProperty(QQSK::Property::Spacing, spacing))
+ handleStylePropertyChanged(&QQStyleKitControlProperties::spacingChanged);
+}
+
+qreal QQStyleKitControlProperties::padding() const
+{
+ return styleProperty<qreal>(QQSK::Property::Padding);
+}
+
+void QQStyleKitControlProperties::setPadding(qreal padding)
+{
+ if (setStyleProperty(QQSK::Property::Padding, padding))
+ handleStylePropertiesChanged<QQStyleKitControlProperties>(
+ &QQStyleKitControlProperties::paddingChanged,
+ &QQStyleKitControlProperties::leftPaddingChanged,
+ &QQStyleKitControlProperties::rightPaddingChanged,
+ &QQStyleKitControlProperties::topPaddingChanged,
+ &QQStyleKitControlProperties::bottomPaddingChanged);
+}
+
+qreal QQStyleKitControlProperties::leftPadding() const
+{
+ return styleProperty<qreal>(QQSK::Property::LeftPadding, QQSK::Property::Padding);
+}
+
+void QQStyleKitControlProperties::setLeftPadding(qreal leftPadding)
+{
+ if (setStyleProperty(QQSK::Property::LeftPadding, leftPadding))
+ handleStylePropertyChanged(&QQStyleKitControlProperties::leftPaddingChanged);
+}
+
+qreal QQStyleKitControlProperties::rightPadding() const
+{
+ return styleProperty<qreal>(QQSK::Property::RightPadding, QQSK::Property::Padding);
+}
+
+void QQStyleKitControlProperties::setRightPadding(qreal rightPadding)
+{
+ if (setStyleProperty(QQSK::Property::RightPadding, rightPadding))
+ handleStylePropertyChanged(&QQStyleKitControlProperties::rightPaddingChanged);
+}
+
+qreal QQStyleKitControlProperties::topPadding() const
+{
+ return styleProperty<qreal>(QQSK::Property::TopPadding, QQSK::Property::Padding);
+}
+
+void QQStyleKitControlProperties::setTopPadding(qreal topPadding)
+{
+ if (setStyleProperty(QQSK::Property::TopPadding, topPadding))
+ handleStylePropertyChanged(&QQStyleKitControlProperties::topPaddingChanged);
+}
+
+qreal QQStyleKitControlProperties::bottomPadding() const
+{
+ return styleProperty<qreal>(QQSK::Property::BottomPadding, QQSK::Property::Padding);
+}
+
+void QQStyleKitControlProperties::setBottomPadding(qreal bottomPadding)
+{
+ if (setStyleProperty(QQSK::Property::BottomPadding, bottomPadding))
+ handleStylePropertyChanged(&QQStyleKitControlProperties::bottomPaddingChanged);
+}
+
+QQuickTransition *QQStyleKitControlProperties::transition() const
+{
+ return styleProperty<QQuickTransition *>(QQSK::Property::Transition);
+}
+
+void QQStyleKitControlProperties::setTransition(QQuickTransition *transition)
+{
+ if (setStyleProperty(QQSK::Property::Transition, transition))
+ handleStylePropertyChanged(&QQStyleKitControlProperties::transitionChanged);
+}
+
+QQStyleKitTextProperties *QQStyleKitControlProperties::text() const
+{
+ return lazyCreateGroup(m_text, QQSK::PropertyGroup::Text);
+}
+
+QQStyleKitDelegateProperties *QQStyleKitControlProperties::background() const
+{
+ return lazyCreateGroup(m_background, QQSK::PropertyGroup::Background);
+}
+
+QQStyleKitHandleProperties *QQStyleKitControlProperties::handle() const
+{
+ return lazyCreateGroup(m_handle, QQSK::PropertyGroup::Handle);
+}
+
+QQStyleKitIndicatorWithSubTypes *QQStyleKitControlProperties::indicator() const
+{
+ return lazyCreateGroup(m_indicator, QQSK::PropertyGroup::Indicator);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitcontrolproperties_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitcontrolproperties_p.h b/src/labs/stylekit/qqstylekitcontrolproperties_p.h
new file mode 100644
index 0000000000..5bd04c3fd9
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrolproperties_p.h
@@ -0,0 +1,591 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITCONTROLPROPERTIES_P_H
+#define QQSTYLEKITCONTROLPROPERTIES_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 <QtQml/QtQml>
+#include <QtGui/QColor>
+#include <QtQuick/private/qquickrectangle_p.h>
+#include <QtQuick/private/qquickimage_p.h>
+#include <QtQuick/private/qquicktransition_p.h>
+#include <QtQuick/private/qquicktext_p.h>
+
+#include "qqstylekitglobal_p.h"
+#include "qqstylekitpropertyresolver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+class QQStyleKitStyle;
+class QQStyleKitControl;
+class QQStyleKitControlState;
+class QQStyleKitReader;
+class QQStyleKitControlProperties;
+class QQStyleKitDelegateProperties;
+
+class QQStyleKitPropertyGroup: public QObject
+{
+ Q_OBJECT
+
+public:
+ QQStyleKitPropertyGroup(QQSK::PropertyGroup group, QObject *parent);
+
+ inline bool isControlProperties() const { return m_group == QQSK::PropertyGroup::Control; }
+ inline bool isPathFlag() const { return m_group == QQSK::PropertyGroup::globalFlag; }
+ inline bool isDelegateSubType() const {
+ return m_group == QQSK::PropertyGroup::DelegateSubType1 ||
+ m_group == QQSK::PropertyGroup::DelegateSubType2;
+ }
+
+ inline QQSK::PropertyGroup group() const { return m_group; }
+
+ template<typename T>
+ inline T styleProperty(
+ QQSK::Property property,
+ QQSK::Property alternative = QQSK::Property::NoProperty) const
+ {
+ return qvariant_cast<T>(QQStyleKitPropertyResolver::readStyleProperty(this, property, alternative));
+ }
+
+ template<typename T>
+ inline T styleProperty(QQSK::Property property, const T &defaultValue) const
+ {
+ const QVariant value = QQStyleKitPropertyResolver::readStyleProperty(this, property);
+ return value.isValid() ? qvariant_cast<T>(value) : defaultValue;
+ }
+
+ template<typename T>
+ inline bool setStyleProperty(QQSK::Property property, T value)
+ {
+ // This function will return true if the new value differes from the old one
+ return QQStyleKitPropertyResolver::writeStyleProperty(this, property, QVariant::fromValue(value));
+ }
+
+ template<typename SUBCLASS>
+ inline void handleStylePropertyChanged(void (SUBCLASS::*changedSignal)());
+
+ template <typename SUBCLASS, typename... CHANGED_SIGNALS>
+ inline void handleStylePropertiesChanged(CHANGED_SIGNALS... changedSignals);
+
+ template <typename T>
+ inline T *lazyCreateGroup(T *const &ptr, QQSK::PropertyGroup group) const
+ {
+ return QQSK::lazyCreate(ptr, this, group);
+ }
+
+ void emitChangedForAllStylePropertiesRecursive();
+
+ const QQStyleKitControlProperties *controlProperties() const;
+
+ std::tuple<
+ const QQStyleKitControlProperties *,
+ const QQSK::PropertyGroup,
+ const QQSK::PathFlags> inspectGroupPath() const;
+
+ inline QQStyleKitPropertyGroup *parentGroup() const {
+ Q_ASSERT(!parent() || qobject_cast<QQStyleKitPropertyGroup *>(parent()));
+ return static_cast<QQStyleKitPropertyGroup *>(parent());
+ }
+
+ const QQStyleKitControlProperties *asControlProperties() const;
+
+private:
+ bool shouldEmitLocally();
+ bool shouldEmitGlobally();
+
+private:
+ QQSK::PropertyGroup m_group;
+};
+
+// ************* QQStyleKitImageProperties ****************
+
+class QQStyleKitImageProperties : public QQStyleKitPropertyGroup
+{
+ Q_OBJECT
+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged FINAL)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(QQuickImage::FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitImageProperties)
+
+public:
+ QQStyleKitImageProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ QUrl source() const;
+ void setSource(const QUrl &source);
+
+ QColor color() const;
+ void setColor(const QColor &color);
+
+ QQuickImage::FillMode fillMode() const;
+ void setFillMode(QQuickImage::FillMode fillMode);
+
+signals:
+ void sourceChanged();
+ void colorChanged();
+ void fillModeChanged();
+};
+
+// ************* QQStyleKitBorderProperties ****************
+
+class QQStyleKitBorderProperties : public QQStyleKitPropertyGroup
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged FINAL)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitBorderProperties)
+
+public:
+ QQStyleKitBorderProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ qreal width() const;
+ void setWidth(qreal width);
+
+ QColor color() const;
+ void setColor(const QColor &color);
+
+signals:
+ void widthChanged();
+ void colorChanged();
+};
+
+// ************* QQStyleKitShadowProperties ****************
+
+class QQStyleKitShadowProperties : public QQStyleKitPropertyGroup
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL)
+ Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged FINAL)
+ Q_PROPERTY(qreal verticalOffset READ verticalOffset WRITE setVerticalOffset NOTIFY verticalOffsetChanged FINAL)
+ Q_PROPERTY(qreal horizontalOffset READ horizontalOffset WRITE setHorizontalOffset NOTIFY horizontalOffsetChanged FINAL)
+ Q_PROPERTY(qreal blur READ blur WRITE setBlur NOTIFY blurChanged FINAL)
+ Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged FINAL)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitShadowProperties)
+
+public:
+ QQStyleKitShadowProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ QColor color() const;
+ void setColor(QColor color);
+
+ qreal opacity() const;
+ void setOpacity(qreal opacity);
+
+ qreal scale() const;
+ void setScale(qreal scale);
+
+ qreal verticalOffset() const;
+ void setVerticalOffset(qreal verticalOffset);
+
+ qreal horizontalOffset() const;
+ void setHorizontalOffset(qreal horizontalOffset);
+
+ qreal blur() const;
+ void setBlur(qreal blur);
+
+ bool visible() const;
+ void setVisible(bool visible);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *delegate);
+
+signals:
+ void colorChanged();
+ void opacityChanged();
+ void scaleChanged();
+ void verticalOffsetChanged();
+ void horizontalOffsetChanged();
+ void blurChanged();
+ void visibleChanged();
+ void delegateChanged();
+};
+
+// ************* QQStyleKitDelegateProperties ****************
+
+class QQStyleKitDelegateProperties : public QQStyleKitPropertyGroup
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal implicitWidth READ implicitWidth WRITE setImplicitWidth NOTIFY implicitWidthChanged FINAL)
+ Q_PROPERTY(qreal implicitHeight READ implicitHeight WRITE setImplicitHeight NOTIFY implicitHeightChanged FINAL)
+ Q_PROPERTY(qreal minimumWidth READ minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged FINAL)
+ Q_PROPERTY(qreal margins READ margins WRITE setMargins NOTIFY marginsChanged FINAL)
+ Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged FINAL)
+ Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged FINAL)
+ Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged FINAL)
+ Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged FINAL)
+ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL)
+ Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
+ Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged FINAL)
+ Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged FINAL)
+ Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged FINAL)
+ Q_PROPERTY(qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged FINAL)
+ Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged FINAL)
+ Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged FINAL)
+ Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged FINAL)
+ Q_PROPERTY(bool clip READ clip WRITE setClip NOTIFY clipChanged FINAL)
+ Q_PROPERTY(QQuickGradient *gradient READ gradient WRITE setGradient NOTIFY gradientChanged FINAL)
+ Q_PROPERTY(QQStyleKitImageProperties *image READ image NOTIFY imageChanged FINAL)
+ Q_PROPERTY(QQStyleKitBorderProperties *border READ border NOTIFY borderChanged FINAL)
+ Q_PROPERTY(QQStyleKitShadowProperties *shadow READ shadow NOTIFY shadowChanged FINAL)
+ Q_PROPERTY(QObject *data READ data WRITE setData NOTIFY dataChanged FINAL)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitDelegateProperties)
+
+public:
+ QQStyleKitDelegateProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ qreal radius() const;
+ void setRadius(qreal radius);
+
+ qreal topLeftRadius() const;
+ void setTopLeftRadius(qreal radius);
+
+ qreal topRightRadius() const;
+ void setTopRightRadius(qreal radius);
+
+ qreal bottomLeftRadius() const;
+ void setBottomLeftRadius(qreal radius);
+
+ qreal bottomRightRadius() const;
+ void setBottomRightRadius(qreal radius);
+
+ qreal scale() const;
+ void setScale(qreal scale);
+
+ qreal rotation() const;
+ void setRotation(qreal rotation);
+
+ qreal implicitWidth() const;
+ void setImplicitWidth(qreal width);
+
+ qreal implicitHeight() const;
+ void setImplicitHeight(qreal height);
+
+ qreal minimumWidth() const;
+ void setMinimumWidth(qreal width);
+
+ qreal margins() const;
+ void setMargins(qreal margins);
+
+ qreal leftMargin() const;
+ void setLeftMargin(qreal margin);
+
+ qreal rightMargin() const;
+ void setRightMargin(qreal margin);
+
+ qreal topMargin() const;
+ void setTopMargin(qreal margin);
+
+ qreal bottomMargin() const;
+ void setBottomMargin(qreal margin);
+
+ Qt::Alignment alignment() const;
+ void setAlignment(Qt::Alignment alignment);
+
+ qreal opacity() const;
+ void setOpacity(qreal opacity);
+
+ QColor color() const;
+ void setColor(const QColor &color);
+
+ bool visible() const;
+ void setVisible(bool visible);
+
+ bool clip() const;
+ void setClip(bool clip);
+
+ QQuickGradient *gradient() const;
+ void setGradient(QQuickGradient *gradient);
+
+ QObject *data() const;
+ void setData(QObject *data);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *delegate);
+
+ QQStyleKitImageProperties *image() const;
+ QQStyleKitBorderProperties *border() const;
+ QQStyleKitShadowProperties *shadow() const;
+
+signals:
+ void colorChanged();
+ void radiusChanged();
+ void topLeftRadiusChanged();
+ void topRightRadiusChanged();
+ void bottomLeftRadiusChanged();
+ void bottomRightRadiusChanged();
+ void scaleChanged();
+ void rotationChanged();
+ void visibleChanged();
+ void clipChanged();
+ void borderChanged();
+ void shadowChanged();
+ void imageChanged();
+ void gradientChanged();
+ void colorImageChanged();
+ void implicitWidthChanged();
+ void implicitHeightChanged();
+ void minimumWidthChanged();
+ void marginsChanged();
+ void leftMarginChanged();
+ void rightMarginChanged();
+ void topMarginChanged();
+ void bottomMarginChanged();
+ void alignmentChanged();
+ void opacityChanged();
+ void dataChanged();
+ void delegateChanged();
+
+private:
+ QPointer<QQuickGradient> m_gradient;
+ QQStyleKitBorderProperties *m_border = nullptr;
+ QQStyleKitShadowProperties *m_shadow = nullptr;
+ QQStyleKitImageProperties *m_image = nullptr;
+};
+
+// ************* QQStyleKitHandleProperties ****************
+
+class QQStyleKitHandleProperties : public QQStyleKitDelegateProperties
+{
+ Q_OBJECT
+ Q_PROPERTY(QQStyleKitDelegateProperties *first READ first NOTIFY firstChanged FINAL)
+ Q_PROPERTY(QQStyleKitDelegateProperties *second READ second NOTIFY secondChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitHandleProperties)
+
+public:
+ QQStyleKitHandleProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+ QQStyleKitDelegateProperties *first() const;
+ QQStyleKitDelegateProperties *second() const;
+
+signals:
+ void firstChanged();
+ void secondChanged();
+
+private:
+ QQStyleKitDelegateProperties *m_first = nullptr;
+ QQStyleKitDelegateProperties *m_second = nullptr;
+
+ friend class QQStyleKitReader;
+ friend class QQStyleKitControlProperties;
+};
+
+// ************* QQStyleKitIndicatorProperties ****************
+
+class QQStyleKitIndicatorProperties : public QQStyleKitDelegateProperties
+{
+ Q_OBJECT
+ Q_PROPERTY(QQStyleKitDelegateProperties *foreground READ foreground NOTIFY foregroundChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitIndicatorProperties)
+
+public:
+ QQStyleKitIndicatorProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ QQStyleKitDelegateProperties *foreground() const;
+
+signals:
+ void foregroundChanged();
+
+private:
+ QQStyleKitDelegateProperties *m_foreground = nullptr;
+
+ friend class QQStyleKitReader;
+ friend class QQStyleKitControlProperties;
+};
+
+// ************* QQStyleKitIndicatorWithSubTypes ****************
+
+class QQStyleKitIndicatorWithSubTypes : public QQStyleKitDelegateProperties
+{
+ Q_OBJECT
+ Q_PROPERTY(QQStyleKitDelegateProperties *foreground READ foreground NOTIFY foregroundChanged FINAL)
+ Q_PROPERTY(QQStyleKitIndicatorProperties *up READ up NOTIFY upChanged FINAL)
+ Q_PROPERTY(QQStyleKitIndicatorProperties *down READ down NOTIFY downChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitIndicatorPropertiesWithSubTypes)
+
+public:
+ QQStyleKitIndicatorWithSubTypes(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ QQStyleKitDelegateProperties *foreground() const;
+ QQStyleKitIndicatorProperties *up() const;
+ QQStyleKitIndicatorProperties *down() const;
+
+signals:
+ void foregroundChanged();
+ void upChanged();
+ void downChanged();
+
+private:
+ QQStyleKitDelegateProperties *m_foreground = nullptr;
+ QQStyleKitIndicatorProperties *m_up = nullptr;
+ QQStyleKitIndicatorProperties *m_down = nullptr;
+
+ friend class QQStyleKitReader;
+ friend class QQStyleKitControlProperties;
+};
+
+// ************* QQStyleKitTextProperties ****************
+
+class QQStyleKitTextProperties : public QQStyleKitPropertyGroup
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL)
+ QML_UNCREATABLE("This component can only be instantiated by StyleKit")
+ QML_NAMED_ELEMENT(StyleKitTextProperties)
+
+public:
+ QQStyleKitTextProperties(QQSK::PropertyGroup group, QQStyleKitPropertyGroup *parent = nullptr);
+
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+
+ QColor color() const;
+ void setColor(const QColor &color);
+
+ Qt::Alignment alignment() const;
+ void setAlignment(Qt::Alignment alignment);
+
+signals:
+ void colorChanged();
+ void alignmentChanged();
+};
+
+/************* QQStyleKitControlProperties ****************
+ * QQStyleKitControlProperties (and all other subclasses of QQStyleKitPropertyGroup),
+ * is just an empty interface declaring the properties available for styling and reading.
+ * It contains as little data as possible since it will be instantiated a lot. E.g a style
+ * will instantiate them for every style.button, style.slider, etc defined, the same for a
+ * theme, and also each instance of a StyleKitReader. Those are all subclasses of this
+ * class. Each subclass will determine if the properties can be written to (as opposed to
+ * only be read), and if so, offer a storage for storing those values. That storage is typically
+ * a map that stores _only_ the properties that are written to, and nothing else.
+ */
+class QQStyleKitControlProperties : public QQStyleKitPropertyGroup
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged FINAL)
+ Q_PROPERTY(qreal padding READ padding WRITE setPadding NOTIFY paddingChanged FINAL)
+ Q_PROPERTY(qreal leftPadding READ leftPadding WRITE setLeftPadding NOTIFY leftPaddingChanged FINAL)
+ Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding NOTIFY rightPaddingChanged FINAL)
+ Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding NOTIFY topPaddingChanged FINAL)
+ Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding NOTIFY bottomPaddingChanged FINAL)
+ Q_PROPERTY(QQStyleKitDelegateProperties *background READ background NOTIFY backgroundChanged FINAL)
+ Q_PROPERTY(QQStyleKitHandleProperties *handle READ handle NOTIFY handleChanged FINAL)
+ Q_PROPERTY(QQStyleKitIndicatorWithSubTypes *indicator READ indicator NOTIFY indicatorChanged FINAL)
+ Q_PROPERTY(QQStyleKitTextProperties *text READ text NOTIFY textChanged FINAL)
+ Q_PROPERTY(QQuickTransition *transition READ transition WRITE setTransition NOTIFY transitionChanged FINAL)
+ QML_UNCREATABLE("This component acts as an interface, and cannot be instantiated")
+ QML_NAMED_ELEMENT(StyleKitControlProperties)
+
+public:
+ QQStyleKitControlProperties(QQSK::PropertyGroup group, QObject *parent = nullptr);
+
+ void emitChangedForAllStyleProperties();
+ template <typename... CHANGED_SIGNALS>
+ void emitGlobally(CHANGED_SIGNALS... changedSignals) const;
+ void forEachUsedDelegate(
+ std::function<void (QQStyleKitDelegateProperties *, QQSK::Delegate, const QString &)> f);
+
+ QQStyleKitStyle *style() const;
+ QQSK::Subclass subclass() const;
+ QQStyleKitReader *asQQStyleKitReader() const;
+ QQStyleKitControlState *asQQStyleKitState() const;
+ QQStyleKitControl *asQQStyleKitControl() const;
+
+ qreal spacing() const;
+ void setSpacing(qreal spacing);
+
+ qreal padding() const;
+ void setPadding(qreal padding);
+
+ qreal leftPadding() const;
+ void setLeftPadding(qreal leftPadding);
+
+ qreal rightPadding() const;
+ void setRightPadding(qreal rightPadding);
+
+ qreal topPadding() const;
+ void setTopPadding(qreal topPadding);
+
+ qreal bottomPadding() const;
+ void setBottomPadding(qreal bottomPadding);
+
+ QQuickTransition* transition() const;
+ void setTransition(QQuickTransition *transition);
+
+ QQStyleKitTextProperties *text() const;
+
+ QQStyleKitDelegateProperties *background() const;
+ QQStyleKitHandleProperties *handle() const;
+ QQStyleKitIndicatorWithSubTypes *indicator() const;
+
+signals:
+ void backgroundChanged();
+ void handleChanged();
+ void indicatorChanged();
+ void spacingChanged();
+ void paddingChanged();
+ void leftPaddingChanged();
+ void rightPaddingChanged();
+ void topPaddingChanged();
+ void bottomPaddingChanged();
+ void transitionChanged();
+ void textChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitControlProperties)
+
+ QQStyleKitDelegateProperties *m_background = nullptr;
+ QQStyleKitHandleProperties *m_handle = nullptr;
+ QQStyleKitIndicatorWithSubTypes *m_indicator = nullptr;
+ QQStyleKitTextProperties *m_text = nullptr;
+
+ friend class QQStyleKitPropertyResolver;
+ friend class QQStyleKitReader;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITCONTROLPROPERTIES_P_H
diff --git a/src/labs/stylekit/qqstylekitcontrols.cpp b/src/labs/stylekit/qqstylekitcontrols.cpp
new file mode 100644
index 0000000000..d639d5bfec
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrols.cpp
@@ -0,0 +1,92 @@
+// 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
+
+#include "qqstylekitcontrols_p.h"
+#include "qqstylekitcontrol_p.h"
+#include "qqstylekitcustomcontrol_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QQStyleKitControls::QQStyleKitControls(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QQmlListProperty<QObject> QQStyleKitControls::data()
+{
+ return QQmlListProperty<QObject>(this, &m_data);
+}
+
+const QList<QObject *> QQStyleKitControls::children() const
+{
+ return m_data;
+}
+
+/* Lazy-create the controls that the style is actually using, when accessed
+ * them from the style/application (e.g from Style or Theme). We don't lazy
+ * create any controls while resolving style properties, as undefined controls would
+ * anyway not contain any property overrides. The properties have setters too, to
+ * allow the style/application to share custom StyleKitControls the classical
+ * way, e.g button: StyleKitControl { id: button }. */
+QQStyleKitControl* QQStyleKitControls::getControl(int controlType) const
+{
+ if (!m_controls.contains(controlType))
+ return nullptr;
+ return m_controls[controlType];
+}
+
+#define IMPLEMENT_ACCESSORS(NAME, TYPE) \
+QQStyleKitControl *QQStyleKitControls::NAME() const \
+{ \
+ if (!m_controls.contains(TYPE)) { \
+ auto *self = const_cast<QQStyleKitControls *>(this); \
+ auto *control = new QQStyleKitControl(self); \
+ self->m_controls.insert(TYPE, control); \
+ } \
+ return m_controls[TYPE]; \
+} \
+void QQStyleKitControls::set_ ## NAME(QQStyleKitControl *control) \
+{ \
+ m_controls.insert(TYPE, control); \
+}
+
+IMPLEMENT_ACCESSORS(abstractButton, QQStyleKitReader::ControlType::AbstractButton)
+IMPLEMENT_ACCESSORS(control, QQStyleKitReader::ControlType::Control)
+IMPLEMENT_ACCESSORS(button, QQStyleKitReader::ControlType::Button)
+IMPLEMENT_ACCESSORS(flatButton, QQStyleKitReader::ControlType::FlatButton)
+IMPLEMENT_ACCESSORS(checkBox, QQStyleKitReader::ControlType::CheckBox)
+IMPLEMENT_ACCESSORS(comboBox, QQStyleKitReader::ControlType::ComboBox)
+IMPLEMENT_ACCESSORS(slider, QQStyleKitReader::ControlType::Slider)
+IMPLEMENT_ACCESSORS(spinBox, QQStyleKitReader::ControlType::SpinBox)
+IMPLEMENT_ACCESSORS(switchControl, QQStyleKitReader::ControlType::SwitchControl)
+IMPLEMENT_ACCESSORS(textField, QQStyleKitReader::ControlType::TextField)
+IMPLEMENT_ACCESSORS(textInput, QQStyleKitReader::ControlType::TextInput)
+IMPLEMENT_ACCESSORS(radioButton, QQStyleKitReader::ControlType::RadioButton)
+IMPLEMENT_ACCESSORS(itemDelegate, QQStyleKitReader::ControlType::ItemDelegate)
+IMPLEMENT_ACCESSORS(popup, QQStyleKitReader::ControlType::Popup)
+IMPLEMENT_ACCESSORS(pane, QQStyleKitReader::ControlType::Pane)
+IMPLEMENT_ACCESSORS(page, QQStyleKitReader::ControlType::Page)
+IMPLEMENT_ACCESSORS(frame, QQStyleKitReader::ControlType::Frame)
+
+#undef IMPLEMENT_ACCESSORS
+
+void QQStyleKitControls::componentComplete()
+{
+ for (auto *obj : children()) {
+ if (auto *customControl = qobject_cast<QQStyleKitCustomControl *>(obj)) {
+ const int type = customControl->controlType();
+ const int reserved = int(QQStyleKitReader::ControlType::Unknown);
+ if (type >= reserved)
+ qmlWarning(this) << "CustomControls must use a controlType less than " << reserved;
+ if (m_controls.contains(type))
+ qmlWarning(this) << "CustomControl registered more than once: " << type;
+ m_controls.insert(type, customControl);
+ }
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitcontrols_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitcontrols_p.h b/src/labs/stylekit/qqstylekitcontrols_p.h
new file mode 100644
index 0000000000..673d108328
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrols_p.h
@@ -0,0 +1,122 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITCONTROLS_P_H
+#define QQSTYLEKITCONTROLS_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 <QtQml/QtQml>
+
+#include "qqstylekitreader_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitStyle;
+class QQStyleKitControl;
+class QQStyleKitCustomControl;
+
+class QQStyleKitControls : public QObject, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+
+ Q_PROPERTY(QQStyleKitControl *abstractButton READ abstractButton WRITE set_abstractButton NOTIFY abstractButtonChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *control READ control WRITE set_control NOTIFY controlChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *button READ button WRITE set_button NOTIFY buttonChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *checkBox READ checkBox WRITE set_checkBox NOTIFY checkBoxChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *comboBox READ comboBox WRITE set_comboBox NOTIFY comboBoxChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *flatButton READ flatButton WRITE set_flatButton NOTIFY flatButtonChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *slider READ slider WRITE set_slider NOTIFY sliderChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *spinBox READ spinBox WRITE set_spinBox NOTIFY spinBoxChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *textField READ textField WRITE set_textField NOTIFY textFieldChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *textInput READ textInput WRITE set_textInput NOTIFY textInputChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *switchControl READ switchControl WRITE set_switchControl NOTIFY switchControlChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *radioButton READ radioButton WRITE set_radioButton NOTIFY radioButtonChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *itemDelegate READ itemDelegate WRITE set_itemDelegate NOTIFY itemDelegateChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *popup READ popup WRITE set_popup NOTIFY popupChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *pane READ pane WRITE set_pane NOTIFY paneChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *page READ page WRITE set_page NOTIFY pageChanged FINAL)
+ Q_PROPERTY(QQStyleKitControl *frame READ frame WRITE set_frame NOTIFY frameChanged FINAL)
+ QML_UNCREATABLE("This component is abstract, and cannot be instantiated")
+ QML_NAMED_ELEMENT(StyleKitControls)
+
+ Q_PROPERTY(QQmlListProperty<QObject> data READ data NOTIFY dataChanged FINAL)
+ Q_CLASSINFO("DefaultProperty", "data")
+
+public:
+ QQStyleKitControls(QObject *parent = nullptr);
+
+#define IMPLEMENT_ACCESSORS(NAME) \
+ QQStyleKitControl *NAME() const; \
+ void set_ ## NAME(QQStyleKitControl *control);
+
+ IMPLEMENT_ACCESSORS(abstractButton)
+ IMPLEMENT_ACCESSORS(control)
+ IMPLEMENT_ACCESSORS(button)
+ IMPLEMENT_ACCESSORS(checkBox)
+ IMPLEMENT_ACCESSORS(comboBox)
+ IMPLEMENT_ACCESSORS(flatButton)
+ IMPLEMENT_ACCESSORS(slider)
+ IMPLEMENT_ACCESSORS(spinBox)
+ IMPLEMENT_ACCESSORS(textField)
+ IMPLEMENT_ACCESSORS(textInput)
+ IMPLEMENT_ACCESSORS(switchControl)
+ IMPLEMENT_ACCESSORS(radioButton)
+ IMPLEMENT_ACCESSORS(itemDelegate)
+ IMPLEMENT_ACCESSORS(popup)
+ IMPLEMENT_ACCESSORS(pane)
+ IMPLEMENT_ACCESSORS(page)
+ IMPLEMENT_ACCESSORS(frame)
+
+#undef IMPLEMENT_ACCESSORS
+
+ Q_INVOKABLE QQStyleKitControl *getControl(int controlType) const;
+
+ QQmlListProperty<QObject> data();
+ const QList<QObject *> children() const;
+
+signals:
+ void dataChanged();
+ void abstractButtonChanged();
+ void controlChanged();
+ void buttonChanged();
+ void checkBoxChanged();
+ void comboBoxChanged();
+ void flatButtonChanged();
+ void sliderChanged();
+ void spinBoxChanged();
+ void textFieldChanged();
+ void textInputChanged();
+ void switchControlChanged();
+ void radioButtonChanged();
+ void itemDelegateChanged();
+ void popupChanged();
+ void paneChanged();
+ void pageChanged();
+ void frameChanged();
+
+protected:
+ void classBegin() override {}
+ void componentComplete() override;
+
+private:
+ Q_DISABLE_COPY(QQStyleKitControls)
+
+ QList<QObject *> m_data;
+ QMap<int, QQStyleKitControl *> m_controls;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITCONTROLS_P_H
diff --git a/src/labs/stylekit/qqstylekitcontrolstate.cpp b/src/labs/stylekit/qqstylekitcontrolstate.cpp
new file mode 100644
index 0000000000..f31ec969b4
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrolstate.cpp
@@ -0,0 +1,99 @@
+// 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
+
+#include "qqstylekitcontrolstate_p.h"
+#include "qqstylekitcontrol_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitControlState::QQStyleKitControlState(QObject *parent)
+ : QQStyleKitControlProperties(QQSK::PropertyGroup::Control, parent)
+{
+}
+
+#define IMPLEMENT_ACCESSORS(STATE) \
+QQStyleKitControlState *QQStyleKitControlState::STATE() const \
+{ \
+ if (!m_ ## STATE) { \
+ auto *self = const_cast<QQStyleKitControlState *>(this); \
+ self->m_ ## STATE = new QQStyleKitControlState(self); \
+ } \
+ return m_ ## STATE; \
+}
+
+IMPLEMENT_ACCESSORS(pressed);
+IMPLEMENT_ACCESSORS(hovered);
+IMPLEMENT_ACCESSORS(highlighted);
+IMPLEMENT_ACCESSORS(focused);
+IMPLEMENT_ACCESSORS(checked);
+IMPLEMENT_ACCESSORS(vertical);
+IMPLEMENT_ACCESSORS(disabled);
+
+std::tuple<QQStyleKitControl *, QQSK::State>
+QQStyleKitControlState::controlAndState()
+{
+ /* Follow the parent path back to the QQStyleKitControl, and
+ * track which nested states we're in along the way. The path of
+ * states determines the state of the control that the properties
+ * inside this QQStyleKitControlState should apply for. */
+ QQStyleKitControl *control = nullptr;
+ QQSK::State nestedState = QQSK::StateFlag::NoState;
+ const QQStyleKitControlState *obj = this;
+
+ if (metaObject()->inherits(&QQStyleKitControl::staticMetaObject))
+ control = asQQStyleKitControl();
+
+ while (true) {
+ QQStyleKitControlState *parentState = qobject_cast<QQStyleKitControlState *>(obj->parent());
+ if (!parentState)
+ break;
+
+ if (obj == parentState->pressed())
+ nestedState.setFlag(QQSK::StateFlag::Pressed);
+ else if (obj == parentState->hovered())
+ nestedState.setFlag(QQSK::StateFlag::Hovered);
+ else if (obj == parentState->focused())
+ nestedState.setFlag(QQSK::StateFlag::Focused);
+ else if (obj == parentState->highlighted())
+ nestedState.setFlag(QQSK::StateFlag::Highlighted);
+ else if (obj == parentState->checked())
+ nestedState.setFlag(QQSK::StateFlag::Checked);
+ else if (obj == parentState->vertical())
+ nestedState.setFlag(QQSK::StateFlag::Vertical);
+ else if (obj == parentState->disabled())
+ nestedState.setFlag(QQSK::StateFlag::Disabled);
+ else
+ Q_UNREACHABLE();
+
+ obj = parentState;
+ if (obj->metaObject()->inherits(&QQStyleKitControl::staticMetaObject))
+ control = obj->asQQStyleKitControl();
+ }
+
+ if (nestedState.testFlag(QQSK::StateFlag::Disabled)) {
+ nestedState.setFlag(QQSK::StateFlag::Pressed, false);
+ nestedState.setFlag(QQSK::StateFlag::Hovered, false);
+ nestedState.setFlag(QQSK::StateFlag::Focused, false);
+ nestedState.setFlag(QQSK::StateFlag::Highlighted, false);
+ }
+
+ if (nestedState == QQSK::StateFlag::NoState)
+ nestedState = QQSK::StateFlag::Normal;
+
+ Q_ASSERT(control);
+ Q_ASSERT(qlonglong(nestedState) <= qlonglong(QQSK::StateFlag::MAX_STATE));
+
+ return std::make_tuple(control, nestedState);
+}
+
+QQStyleKitControlState *QQStyleKitControlState::parentState() const
+{
+ Q_ASSERT(subclass() == QQSK::Subclass::QQStyleKitState);
+ QObject *p = parent();
+ Q_ASSERT(p && qobject_cast<QQStyleKitControlState *>(p));
+ return static_cast<QQStyleKitControlState *>(p);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitcontrolstate_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitcontrolstate_p.h b/src/labs/stylekit/qqstylekitcontrolstate_p.h
new file mode 100644
index 0000000000..e9b7b70e86
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcontrolstate_p.h
@@ -0,0 +1,76 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITCONTROLSTATE_P_H
+#define QQSTYLEKITCONTROLSTATE_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 <QtQml/QtQml>
+
+#include "qqstylekitcontrolproperties_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitControl;
+
+class QQStyleKitControlState : public QQStyleKitControlProperties
+{
+ Q_OBJECT
+ Q_PROPERTY(QQStyleKitControlState *pressed READ pressed NOTIFY pressedChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlState *hovered READ hovered NOTIFY hoveredChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlState *focused READ focused NOTIFY focusedChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlState *checked READ checked NOTIFY checkedChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlState *disabled READ disabled NOTIFY disabledChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlState *highlighted READ highlighted NOTIFY highlightedChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlState *vertical READ vertical NOTIFY verticalChanged FINAL)
+ QML_NAMED_ELEMENT(StyleKitControlState)
+
+public:
+ QQStyleKitControlState(QObject *parent = nullptr);
+
+ QQStyleKitControlState *pressed() const;
+ QQStyleKitControlState *hovered() const;
+ QQStyleKitControlState *focused() const;
+ QQStyleKitControlState *checked() const;
+ QQStyleKitControlState *disabled() const;
+ QQStyleKitControlState *highlighted() const;
+ QQStyleKitControlState *vertical() const;
+
+ QQStyleKitControlState *parentState() const;
+ std::tuple<QQStyleKitControl *, QQSK::State> controlAndState();
+
+signals:
+ void pressedChanged();
+ void hoveredChanged();
+ void focusedChanged();
+ void checkedChanged();
+ void disabledChanged();
+ void highlightedChanged();
+ void verticalChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitControlState)
+
+ mutable QPointer<QQStyleKitControlState> m_pressed;
+ mutable QPointer<QQStyleKitControlState> m_hovered;
+ mutable QPointer<QQStyleKitControlState> m_focused;
+ mutable QPointer<QQStyleKitControlState> m_checked;
+ mutable QPointer<QQStyleKitControlState> m_disabled;
+ mutable QPointer<QQStyleKitControlState> m_highlighted;
+ mutable QPointer<QQStyleKitControlState> m_vertical;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITCONTROLSTATE_P_H
diff --git a/src/labs/stylekit/qqstylekitcustomcontrol.cpp b/src/labs/stylekit/qqstylekitcustomcontrol.cpp
new file mode 100644
index 0000000000..316a0f4856
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcustomcontrol.cpp
@@ -0,0 +1,32 @@
+// 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
+
+#include "qqstylekitcustomcontrol_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QQStyleKitCustomControl::QQStyleKitCustomControl(QObject *parent)
+ : QQStyleKitControl(parent)
+{
+}
+
+int QQStyleKitCustomControl::controlType() const
+{
+ return m_controlType;
+}
+
+void QQStyleKitCustomControl::setControlType(int controlType)
+{
+ if (m_controlType == controlType)
+ return;
+
+ m_controlType = controlType;
+
+ emit controlTypeChanged();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitcustomcontrol_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitcustomcontrol_p.h b/src/labs/stylekit/qqstylekitcustomcontrol_p.h
new file mode 100644
index 0000000000..40fc666693
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcustomcontrol_p.h
@@ -0,0 +1,47 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITCUSTOMCONTROL_P_H
+#define QQSTYLEKITCUSTOMCONTROL_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 <QtQml/QtQml>
+#include "qqstylekitcontrol_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitCustomControl: public QQStyleKitControl
+{
+ Q_OBJECT
+ Q_PROPERTY(int controlType READ controlType WRITE setControlType NOTIFY controlTypeChanged FINAL)
+ QML_NAMED_ELEMENT(CustomControl)
+
+public:
+ QQStyleKitCustomControl(QObject *parent = nullptr);
+
+ int controlType() const;
+ void setControlType(int controlType);
+
+signals:
+ void controlTypeChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitCustomControl)
+
+ int m_controlType = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITCUSTOMCONTROL_P_H
diff --git a/src/labs/stylekit/qqstylekitcustomtheme.cpp b/src/labs/stylekit/qqstylekitcustomtheme.cpp
new file mode 100644
index 0000000000..5944ba550c
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcustomtheme.cpp
@@ -0,0 +1,42 @@
+// 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
+
+#include "qqstylekitcustomtheme_p.h"
+#include "qqstylekittheme_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitCustomTheme::QQStyleKitCustomTheme(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QString QQStyleKitCustomTheme::name() const
+{
+ return m_name;
+}
+
+void QQStyleKitCustomTheme::setName(const QString &newName)
+{
+ if (m_name == newName)
+ return;
+ m_name = newName;
+ emit nameChanged();
+}
+
+QQmlComponent *QQStyleKitCustomTheme::theme() const
+{
+ return m_theme;
+}
+
+void QQStyleKitCustomTheme::setTheme(QQmlComponent *newTheme)
+{
+ if (m_theme == newTheme)
+ return;
+ m_theme = newTheme;
+ emit themeChanged();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitcustomtheme_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitcustomtheme_p.h b/src/labs/stylekit/qqstylekitcustomtheme_p.h
new file mode 100644
index 0000000000..54b8f24bf4
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitcustomtheme_p.h
@@ -0,0 +1,54 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITCUSTOMTHEME_P_H
+#define QQSTYLEKITCUSTOMTHEME_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 <QtQml/QtQml>
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitTheme;
+
+class QQStyleKitCustomTheme : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
+ Q_PROPERTY(QQmlComponent *theme READ theme WRITE setTheme NOTIFY themeChanged FINAL)
+ QML_NAMED_ELEMENT(CustomTheme)
+
+public:
+ QQStyleKitCustomTheme(QObject *parent = nullptr);
+
+ QString name() const;
+ void setName(const QString &newName);
+
+ QQmlComponent *theme() const;
+ void setTheme(QQmlComponent *newTheme);
+
+Q_SIGNALS:
+ void nameChanged();
+ void themeChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitCustomTheme)
+
+ QString m_name;
+ QQmlComponent *m_theme = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITCUSTOMTHEME_P_H
diff --git a/src/labs/stylekit/qqstylekitdebug.cpp b/src/labs/stylekit/qqstylekitdebug.cpp
new file mode 100644
index 0000000000..dcd191959d
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitdebug.cpp
@@ -0,0 +1,361 @@
+// 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
+
+#include "qqstylekitdebug_p.h"
+#include "qqstylekitcontrol_p.h"
+#include "qqstylekitcontrols_p.h"
+#include "qqstylekitstyle_p.h"
+#include "qqstylekittheme_p.h"
+
+QT_BEGIN_NAMESPACE
+
+const QQStyleKitPropertyGroup *QQStyleKitDebug::groupBeingRead = nullptr;
+QPointer<QQuickItem> QQStyleKitDebug::m_item;
+QString QQStyleKitDebug::m_filter;
+int QQStyleKitDebug::m_outputCount = 0;
+
+static const QChar kDot = '.'_L1;
+
+template <typename EnumType>
+QString QQStyleKitDebug::enumToString(EnumType enumValue)
+{
+ auto propertyMetaEnum = QMetaEnum::fromType<EnumType>();
+ return QString::fromUtf8(propertyMetaEnum.valueToKeys(quint64(enumValue)));
+}
+
+QString QQStyleKitDebug::objectName(const QObject *obj) {
+ QString str = QString::fromLatin1(obj->metaObject()->className());
+ int idx = str.indexOf("_QMLTYPE"_L1);
+ if (idx != -1)
+ str = str.left(idx);
+ else {
+ const QString prefix("QQStyleKit"_L1);
+ if (str.startsWith(prefix))
+ str = str.mid(prefix.length());
+ }
+ const QString name = obj->objectName();
+ if (!name.isEmpty())
+ str = str + "("_L1 + name + ")"_L1;
+ return str;
+}
+
+QString QQStyleKitDebug::stateToString(const QQSK::State state)
+{
+ const QStringList list = enumToString(state).split('|'_L1);
+ return "["_L1 + list.join(','_L1) + "]"_L1;
+}
+
+QString QQStyleKitDebug::styleReaderToString(const QQStyleKitReader *reader)
+{
+ return "StyleKitReader"_L1 + stateToString(reader->controlState());
+}
+
+QString QQStyleKitDebug::propertyPath(const QQStyleKitPropertyGroup *group, const PropertyPathId property)
+{
+ QString path = enumToString(property.property());
+ path[0] = path[0].toLower();
+ const QQStyleKitPropertyGroup *childGroup = group;
+ const int startIndex = QQStyleKitPropertyGroup::staticMetaObject.propertyOffset();
+
+ while (childGroup) {
+ if (childGroup->isControlProperties())
+ break;
+ const QQStyleKitPropertyGroup *parentGroup =
+ qobject_cast<const QQStyleKitPropertyGroup *>(childGroup->parent());
+ if (!parentGroup)
+ break;
+
+ // Resolve group name by inspecting which property in the parent group it belongs to
+ const QMetaObject* parentMeta = parentGroup->metaObject();
+ for (int i = startIndex; i < parentMeta->propertyCount(); ++i) {
+ const QMetaProperty prop = parentMeta->property(i);
+ const QMetaObject* typeMeta = QMetaType::fromName(prop.typeName()).metaObject();
+ if (!typeMeta || !typeMeta->inherits(&QQStyleKitPropertyGroup::staticMetaObject))
+ continue;
+ QObject *propGroup = prop.read(parentGroup).value<QQStyleKitPropertyGroup *>();
+ if (propGroup == childGroup) {
+ path.prepend(QString::fromUtf8(prop.name()) + kDot);
+ break;
+ }
+ }
+
+ childGroup = parentGroup;
+ }
+
+ return path;
+}
+
+QString QQStyleKitDebug::controlToString(const QQStyleKitControlProperties *control)
+{
+ const QObject *parentObj = control->parent();
+ if (!parentObj)
+ return "<no parent>"_L1;
+ auto *controls = qobject_cast<const QQStyleKitControls *>(parentObj);
+ if (!controls) {
+ return "<"_L1 + QString::fromUtf8(parentObj->metaObject()->className()) + ">"_L1;
+ }
+
+ const int startIndex = QQStyleKitControlProperties::staticMetaObject.propertyOffset();
+ const int endIndex = QQStyleKitControlProperties::staticMetaObject.propertyCount();
+
+ const QMetaObject* parentMeta = parentObj->metaObject();
+ for (int i = startIndex; i < endIndex; ++i) {
+ const QMetaProperty prop = parentMeta->property(i);
+ const QMetaObject* typeMeta = QMetaType::fromName(prop.typeName()).metaObject();
+ if (!typeMeta || !typeMeta->inherits(&QQStyleKitControl::staticMetaObject))
+ continue;
+
+ QObject *propObj = prop.read(parentObj).value<QObject *>();
+ if (propObj == control)
+ return QString::fromUtf8(prop.name());
+ }
+ return "<unknown control: no property found>"_L1;
+}
+
+QString QQStyleKitDebug::objectPath(const QQStyleKitControlProperties *properties, QObject *from)
+{
+ QString path;
+ const QObject *obj = properties;
+
+ while (obj) {
+ if (!path.isEmpty())
+ path.prepend(kDot);
+
+ if (auto *theme = qobject_cast<const QQStyleKitCustomTheme *>(obj)) {
+ path.prepend(theme->name() + kDot);
+ } else if (auto *theme = qobject_cast<const QQStyleKitTheme *>(obj)) {
+ // Note: only one theme is instantiated at a time
+ if (auto style = theme->style())
+ path.prepend(style->themeName());
+ else
+ path.prepend(objectName(obj));
+ } else if (auto *control = qobject_cast<const QQStyleKitControl *>(obj)) {
+ path.prepend(controlToString(control));
+ } else if (auto *reader = qobject_cast<const QQStyleKitReader *>(obj)) {
+ path.prepend(styleReaderToString(reader));
+ } else {
+ path.prepend(objectName(obj));
+ }
+
+ if (obj == from)
+ break;
+
+ obj = obj->parent();
+ }
+
+ return path;
+}
+
+void QQStyleKitDebug::notifyPropertyRead(
+ const PropertyPathId property,
+ const QQStyleKitControlProperties *storage,
+ const QQSK::State state,
+ const QVariant &value)
+{
+ Q_ASSERT(enabled());
+
+ const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
+ if (reader->subclass() == QQSK::Subclass::QQStyleKitState) {
+ /* The reader is in the UnfiedStyle, and not in the users application (which can happen
+ * when e.g resolving local bindings between properties in the style). Those are not
+ * interesting to print out when inspecting control-to-style mappings. Ignore. */
+ return;
+ }
+
+ if (!insideControl(reader)) {
+ // We should only debug reads that targets m_item. So return.
+ return;
+ }
+
+ const QString _readerPath = objectPath(reader, m_item);
+ const QString _readPropertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property);
+ const QString queriedPath = _readerPath + kDot +_readPropertyPath;
+
+ QString storagePath;
+ if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
+ /* We read an interpolated value stored directly in the reader itself. While this
+ * can be interesting to print out whe debugging the styling engine itself, it
+ * comes across as noise when inspecting control-to-style mappings. Ignore. */
+#if 0
+ storagePath = "[local storage] "_L1;
+#else
+ return;
+#endif
+ } else {
+ const QString _controlPathInStyle = objectPath(storage, storage->style());
+ const QString _statePath = stateToString(state);
+ storagePath = _controlPathInStyle + _statePath;
+ }
+
+ QString valueString = value.toString();
+ if (!value.isValid()) // value was set, but probably to undefined
+ valueString = "<undefined>"_L1;
+ else if (valueString.isEmpty())
+ valueString = "<object>"_L1;
+
+ const QString output = queriedPath + " -> "_L1 + storagePath + " = "_L1 + valueString;
+
+ if (!QRegularExpression(m_filter).match(output).hasMatch())
+ return;
+
+ qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output;
+}
+
+void QQStyleKitDebug::notifyPropertyWrite(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property,
+ const QQStyleKitControlProperties *storage,
+ const QQSK::State state,
+ const PropertyStorageId key,
+ const QVariant &value)
+{
+#if 1
+ Q_UNUSED(group);
+ Q_UNUSED(property);
+ Q_UNUSED(storage);
+ Q_UNUSED(state);
+ Q_UNUSED(key);
+ Q_UNUSED(value);
+#else
+ /* Note: in order to catch _all_ writes, we cannot depend on enabling writes from
+ * QML using a property, as that would resolve to 'true' too late. */
+ QString storagePath;
+ if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
+ storagePath = "[local storage]"_L1;
+ } else {
+ const QString _controlPathInStyle = objectPath(storage, storage->style());
+ const QString _statePath = stateToString(state);
+ storagePath = _controlPathInStyle + _statePath;
+ }
+
+ QString valueString = value.toString();
+ if (!value.isValid()) // value was set, but probably to undefined
+ valueString = "<undefined>"_L1;
+ else if (valueString.isEmpty())
+ valueString = "<object>"_L1;
+
+ const QString path = propertyPath(group, property);
+ const QString output = storagePath + kDot + path + " (storage key:"_L1 + QString::number(key) + ") = "_L1 + valueString;
+
+ qDebug().nospace().noquote() << m_outputCount++ << " | [write] "_L1 << output;
+#endif
+}
+
+void QQStyleKitDebug::notifyPropertyNotResolved(const PropertyPathId property)
+{
+ const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
+ if (!insideControl(reader)) {
+ // We should only debug reads that targets m_item. So return.
+ return;
+ }
+
+ const QString _readerPath = objectPath(reader, m_item);
+ const QString _propertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property);
+ const QString queriedPath = _readerPath + kDot +_propertyPath;
+ const QString output = queriedPath + " -> <property not set>"_L1;
+
+ if (!QRegularExpression(m_filter).match(output).hasMatch())
+ return;
+
+ qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output;
+}
+
+void QQStyleKitDebug::trace(
+ const PropertyPathId property,
+ const QQStyleKitControlProperties *storage,
+ const QQSK::State state,
+ const PropertyStorageId key)
+{
+#if 1
+ Q_UNUSED(property);
+ Q_UNUSED(storage);
+ Q_UNUSED(state);
+ Q_UNUSED(key);
+#else
+ const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
+ if (reader->subclass() == QQSK::Subclass::QQStyleKitState) {
+ /* The reader is in the UnfiedStyle, and not in the users application (which can happen
+ * when e.g resolving local bindings between properties in the style). Those are not
+ * interesting to print out when inspecting control-to-style mappings. Ignore. */
+ return;
+ }
+
+ if (!insideControl(reader)) {
+ // We should only debug reads that targets m_item. So return.
+ return;
+ }
+
+ const QString _readerPath = objectPath(reader, m_item);
+ const QString _readPropertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property);
+ const QString queriedPath = _readerPath + kDot +_readPropertyPath;
+
+ QString storagePath;
+ if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
+ /* We read an interpolated value stored directly in the reader itself. While this
+ * can be interesting to print out whe debugging the styling engine itself, it
+ * comes across as noise when inspecting control-to-style mappings. Ignore. */
+#if 0
+ storagePath = "[local storage]"_L1;
+#else
+ return;
+#endif
+ } else {
+ const QString _controlPathInStyle = objectPath(storage, storage->style());
+ const QString _statePath = stateToString(state);
+ storagePath = _controlPathInStyle + _statePath;
+ }
+
+ const QString output = queriedPath + ", checking "_L1 + storagePath + " (storage key:"_L1 + QString::number(key)+ ")"_L1;
+
+ if (!QRegularExpression(m_filter).match(output).hasMatch())
+ return;
+
+ qDebug().nospace().noquote() << m_outputCount++ << " | [trace] "_L1 << output;
+#endif
+}
+
+QQuickItem *QQStyleKitDebug::control() const
+{
+ return m_item;
+}
+
+void QQStyleKitDebug::setControl(QQuickItem *item)
+{
+ if (m_item == item)
+ return;
+
+ m_item = item;
+ emit controlChanged();
+}
+
+QString QQStyleKitDebug::filter() const
+{
+ return m_filter;
+}
+
+void QQStyleKitDebug::setFilter(const QString &filter)
+{
+ if (m_filter == filter)
+ return;
+
+ m_filter = filter;
+ emit filterChanged();
+}
+
+bool QQStyleKitDebug::insideControl(const QObject *child)
+{
+ if (!m_item)
+ return false;
+ const QObject *obj = child;
+ do {
+ if (obj == m_item)
+ return true;
+ obj = obj->parent();
+ } while (obj);
+ return false;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitdebug_p.cpp"
+
diff --git a/src/labs/stylekit/qqstylekitdebug_p.h b/src/labs/stylekit/qqstylekitdebug_p.h
new file mode 100644
index 0000000000..4d30da4251
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitdebug_p.h
@@ -0,0 +1,87 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITDEBUG_P_H
+#define QQSTYLEKITDEBUG_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 <QtQuick/qquickitem.h>
+
+#include "qqstylekitcontrolproperties_p.h"
+#include "qqstylekitstorage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitDebug: public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickItem *control READ control WRITE setControl NOTIFY controlChanged FINAL)
+ Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged FINAL)
+ QML_NAMED_ELEMENT(StyleKitDebug)
+
+public:
+ QQuickItem *control() const;
+ void setControl(QQuickItem *item);
+
+ QString filter() const;
+ void setFilter(const QString &filter);
+
+ Q_INVOKABLE static bool insideControl(const QObject *child);
+
+signals:
+ void controlChanged();
+ void filterChanged();
+
+private:
+ static const QQStyleKitPropertyGroup *groupBeingRead;
+ static QPointer<QQuickItem> m_item;
+ static QString m_filter;
+ static int m_outputCount;
+
+private:
+ static inline bool enabled() { return m_item != nullptr; }
+ static void notifyPropertyNotResolved(const PropertyPathId property);
+ static void notifyPropertyRead(
+ const PropertyPathId property,
+ const QQStyleKitControlProperties *resolvedControl,
+ const QQSK::State state,
+ const QVariant &value);
+ static void notifyPropertyWrite(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property,
+ const QQStyleKitControlProperties *storage,
+ const QQSK::State state,
+ const PropertyStorageId key,
+ const QVariant &value);
+ static void trace(
+ const PropertyPathId property,
+ const QQStyleKitControlProperties *resolvedControl,
+ const QQSK::State state,
+ const PropertyStorageId key);
+
+ template <typename EnumType>
+ static QString enumToString(EnumType enumValue);
+ static QString objectName(const QObject *obj);
+ static QString stateToString(const QQSK::State state);
+ static QString styleReaderToString(const QQStyleKitReader *reader);
+ static QString controlToString(const QQStyleKitControlProperties *control);
+ static QString objectPath(const QQStyleKitControlProperties *properties, QObject *from);
+ static QString propertyPath(const QQStyleKitPropertyGroup *group, const PropertyPathId property);
+
+ friend class QQStyleKitPropertyResolver;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITDEBUG_P_H
diff --git a/src/labs/stylekit/qqstylekitfont.cpp b/src/labs/stylekit/qqstylekitfont.cpp
new file mode 100644
index 0000000000..2db4eae309
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitfont.cpp
@@ -0,0 +1,232 @@
+// 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
+
+#include "qqstylekitfont_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitFont::QQStyleKitFont(QObject *parent)
+ : QObject(parent)
+{
+}
+
+#define STYLEKIT_FONT_GETTER(propertyName) \
+ QFont QQStyleKitFont::propertyName() const \
+ { \
+ if (!m_##propertyName) { \
+ m_##propertyName.reset(new QFont()); \
+ } \
+ return *m_##propertyName; \
+ }
+
+STYLEKIT_FONT_GETTER(systemFont)
+STYLEKIT_FONT_GETTER(buttonFont)
+STYLEKIT_FONT_GETTER(checkboxFont)
+STYLEKIT_FONT_GETTER(comboBoxFont)
+STYLEKIT_FONT_GETTER(groupBoxFont)
+STYLEKIT_FONT_GETTER(itemViewFont)
+STYLEKIT_FONT_GETTER(labelFont)
+STYLEKIT_FONT_GETTER(listViewFont)
+STYLEKIT_FONT_GETTER(menuFont)
+STYLEKIT_FONT_GETTER(menuBarFont)
+STYLEKIT_FONT_GETTER(radioButtonFont)
+STYLEKIT_FONT_GETTER(spinBoxFont)
+STYLEKIT_FONT_GETTER(switchControlFont)
+STYLEKIT_FONT_GETTER(tabBarFont)
+STYLEKIT_FONT_GETTER(textAreaFont)
+STYLEKIT_FONT_GETTER(textFieldFont)
+STYLEKIT_FONT_GETTER(toolBarFont)
+STYLEKIT_FONT_GETTER(toolTipFont)
+STYLEKIT_FONT_GETTER(tumblerFont)
+
+void QQStyleKitFont::setSystemFont(const QFont &font)
+{
+ if (!m_systemFont)
+ m_systemFont.reset(new QFont(font));
+ else
+ *m_systemFont = font;
+ emit systemFontChanged();
+}
+
+void QQStyleKitFont::setButtonFont(const QFont &font)
+{
+ if (!m_buttonFont) {
+ m_buttonFont.reset(new QFont(font));
+ } else {
+ *m_buttonFont = font;
+ }
+ emit buttonFontChanged();
+}
+
+void QQStyleKitFont::setCheckboxFont(const QFont &font)
+{
+ if (!m_checkboxFont) {
+ m_checkboxFont.reset(new QFont(font));
+ } else {
+ *m_checkboxFont = font;
+ }
+ emit checkboxFontChanged();
+}
+
+void QQStyleKitFont::setComboBoxFont(const QFont &font)
+{
+ if (!m_comboBoxFont) {
+ m_comboBoxFont.reset(new QFont(font));
+ } else {
+ *m_comboBoxFont = font;
+ }
+ emit comboBoxFontChanged();
+}
+
+void QQStyleKitFont::setGroupBoxFont(const QFont &font)
+{
+ if (!m_groupBoxFont) {
+ m_groupBoxFont.reset(new QFont(font));
+ } else {
+ *m_groupBoxFont = font;
+ }
+ emit groupBoxFontChanged();
+}
+
+void QQStyleKitFont::setItemViewFont(const QFont &font)
+{
+ if (!m_itemViewFont) {
+ m_itemViewFont.reset(new QFont(font));
+ } else {
+ *m_itemViewFont = font;
+ }
+ emit itemViewFontChanged();
+}
+
+void QQStyleKitFont::setLabelFont(const QFont &font)
+{
+ if (!m_labelFont) {
+ m_labelFont.reset(new QFont(font));
+ } else {
+ *m_labelFont = font;
+ }
+ emit labelFontChanged();
+}
+
+void QQStyleKitFont::setListViewFont(const QFont &font)
+{
+ if (!m_listViewFont) {
+ m_listViewFont.reset(new QFont(font));
+ } else {
+ *m_listViewFont = font;
+ }
+ emit listViewFontChanged();
+}
+
+void QQStyleKitFont::setMenuFont(const QFont &font)
+{
+ if (!m_menuFont) {
+ m_menuFont.reset(new QFont(font));
+ } else {
+ *m_menuFont = font;
+ }
+ emit menuFontChanged();
+}
+
+void QQStyleKitFont::setMenuBarFont(const QFont &font)
+{
+ if (!m_menuBarFont) {
+ m_menuBarFont.reset(new QFont(font));
+ } else {
+ *m_menuBarFont = font;
+ }
+ emit menuBarFontChanged();
+}
+
+void QQStyleKitFont::setRadioButtonFont(const QFont &font)
+{
+ if (!m_radioButtonFont) {
+ m_radioButtonFont.reset(new QFont(font));
+ } else {
+ *m_radioButtonFont = font;
+ }
+ emit radioButtonFontChanged();
+}
+
+void QQStyleKitFont::setSpinBoxFont(const QFont &font)
+{
+ if (!m_spinBoxFont) {
+ m_spinBoxFont.reset(new QFont(font));
+ } else {
+ *m_spinBoxFont = font;
+ }
+ emit spinBoxFontChanged();
+}
+
+void QQStyleKitFont::setSwitchControlFont(const QFont &font)
+{
+ if (!m_switchControlFont) {
+ m_switchControlFont.reset(new QFont(font));
+ } else {
+ *m_switchControlFont = font;
+ }
+ emit switchControlFontChanged();
+}
+
+void QQStyleKitFont::setTabBarFont(const QFont &font)
+{
+ if (!m_tabBarFont) {
+ m_tabBarFont.reset(new QFont(font));
+ } else {
+ *m_tabBarFont = font;
+ }
+ emit tabBarFontChanged();
+}
+
+void QQStyleKitFont::setTextAreaFont(const QFont &font)
+{
+ if (!m_textAreaFont) {
+ m_textAreaFont.reset(new QFont(font));
+ } else {
+ *m_textAreaFont = font;
+ }
+ emit textAreaFontChanged();
+}
+
+void QQStyleKitFont::setTextFieldFont(const QFont &font)
+{
+ if (!m_textFieldFont) {
+ m_textFieldFont.reset(new QFont(font));
+ } else {
+ *m_textFieldFont = font;
+ }
+ emit textFieldFontChanged();
+}
+
+void QQStyleKitFont::setToolBarFont(const QFont &font)
+{
+ if (!m_toolBarFont) {
+ m_toolBarFont.reset(new QFont(font));
+ } else {
+ *m_toolBarFont = font;
+ }
+ emit toolBarFontChanged();
+}
+
+void QQStyleKitFont::setToolTipFont(const QFont &font)
+{
+ if (!m_toolTipFont) {
+ m_toolTipFont.reset(new QFont(font));
+ } else {
+ *m_toolTipFont = font;
+ }
+ emit toolTipFontChanged();
+}
+
+void QQStyleKitFont::setTumblerFont(const QFont &font)
+{
+ if (!m_tumblerFont) {
+ m_tumblerFont.reset(new QFont(font));
+ } else {
+ *m_tumblerFont = font;
+ }
+ emit tumblerFontChanged();
+}
+
+QT_END_NAMESPACE
+#include "moc_qqstylekitfont_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitfont_p.h b/src/labs/stylekit/qqstylekitfont_p.h
new file mode 100644
index 0000000000..eeadcdc0f4
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitfont_p.h
@@ -0,0 +1,155 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITFONT_H
+#define QQSTYLEKITFONT_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 <QtQml/QtQml>
+#include <QtGui/qfont.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitFont : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QFont systemFont READ systemFont WRITE setSystemFont NOTIFY systemFontChanged FINAL)
+ Q_PROPERTY(QFont buttonFont READ buttonFont WRITE setButtonFont NOTIFY buttonFontChanged FINAL)
+ Q_PROPERTY(QFont checkboxFont READ checkboxFont WRITE setCheckboxFont NOTIFY checkboxFontChanged FINAL)
+ Q_PROPERTY(QFont comboBoxFont READ comboBoxFont WRITE setComboBoxFont NOTIFY comboBoxFontChanged FINAL)
+ Q_PROPERTY(QFont groupBoxFont READ groupBoxFont WRITE setGroupBoxFont NOTIFY groupBoxFontChanged FINAL)
+ Q_PROPERTY(QFont itemViewFont READ itemViewFont WRITE setItemViewFont NOTIFY itemViewFontChanged FINAL)
+ Q_PROPERTY(QFont labelFont READ labelFont WRITE setLabelFont NOTIFY labelFontChanged FINAL)
+ Q_PROPERTY(QFont listViewFont READ listViewFont WRITE setListViewFont NOTIFY listViewFontChanged FINAL)
+ Q_PROPERTY(QFont menuFont READ menuFont WRITE setMenuFont NOTIFY menuFontChanged FINAL)
+ Q_PROPERTY(QFont menuBarFont READ menuBarFont WRITE setMenuBarFont NOTIFY menuBarFontChanged FINAL)
+ Q_PROPERTY(QFont radioButtonFont READ radioButtonFont WRITE setRadioButtonFont NOTIFY radioButtonFontChanged FINAL)
+ Q_PROPERTY(QFont spinBoxFont READ spinBoxFont WRITE setSpinBoxFont NOTIFY spinBoxFontChanged FINAL)
+ Q_PROPERTY(QFont switchControlFont READ switchControlFont WRITE setSwitchControlFont NOTIFY switchControlFontChanged FINAL)
+ Q_PROPERTY(QFont tabBarFont READ tabBarFont WRITE setTabBarFont NOTIFY tabBarFontChanged FINAL)
+ Q_PROPERTY(QFont textAreaFont READ textAreaFont WRITE setTextAreaFont NOTIFY textAreaFontChanged FINAL)
+ Q_PROPERTY(QFont textFieldFont READ textFieldFont WRITE setTextFieldFont NOTIFY textFieldFontChanged FINAL)
+ Q_PROPERTY(QFont toolBarFont READ toolBarFont WRITE setToolBarFont NOTIFY toolBarFontChanged FINAL)
+ Q_PROPERTY(QFont toolTipFont READ toolTipFont WRITE setToolTipFont NOTIFY toolTipFontChanged FINAL)
+ Q_PROPERTY(QFont tumblerFont READ tumblerFont WRITE setTumblerFont NOTIFY tumblerFontChanged FINAL)
+
+ QML_NAMED_ELEMENT(StyleKitFont)
+
+public:
+ QQStyleKitFont(QObject *parent = nullptr);
+ QFont systemFont() const;
+ void setSystemFont(const QFont &font);
+
+ QFont buttonFont() const;
+ void setButtonFont(const QFont &font);
+
+ QFont checkboxFont() const;
+ void setCheckboxFont(const QFont &font);
+
+ QFont comboBoxFont() const;
+ void setComboBoxFont(const QFont &font);
+
+ QFont groupBoxFont() const;
+ void setGroupBoxFont(const QFont &font);
+
+ QFont itemViewFont() const;
+ void setItemViewFont(const QFont &font);
+
+ QFont labelFont() const;
+ void setLabelFont(const QFont &font);
+
+ QFont listViewFont() const;
+ void setListViewFont(const QFont &font);
+
+ QFont menuFont() const;
+ void setMenuFont(const QFont &font);
+
+ QFont menuBarFont() const;
+ void setMenuBarFont(const QFont &font);
+
+ QFont radioButtonFont() const;
+ void setRadioButtonFont(const QFont &font);
+
+ QFont spinBoxFont() const;
+ void setSpinBoxFont(const QFont &font);
+
+ QFont switchControlFont() const;
+ void setSwitchControlFont(const QFont &font);
+
+ QFont tabBarFont() const;
+ void setTabBarFont(const QFont &font);
+
+ QFont textAreaFont() const;
+ void setTextAreaFont(const QFont &font);
+
+ QFont textFieldFont() const;
+ void setTextFieldFont(const QFont &font);
+
+ QFont toolBarFont() const;
+ void setToolBarFont(const QFont &font);
+
+ QFont toolTipFont() const;
+ void setToolTipFont(const QFont &font);
+
+ QFont tumblerFont() const;
+ void setTumblerFont(const QFont &font);
+
+signals:
+ void systemFontChanged();
+ void buttonFontChanged();
+ void checkboxFontChanged();
+ void comboBoxFontChanged();
+ void groupBoxFontChanged();
+ void itemViewFontChanged();
+ void labelFontChanged();
+ void listViewFontChanged();
+ void menuFontChanged();
+ void menuBarFontChanged();
+ void radioButtonFontChanged();
+ void spinBoxFontChanged();
+ void switchControlFontChanged();
+ void tabBarFontChanged();
+ void textAreaFontChanged();
+ void textFieldFontChanged();
+ void toolBarFontChanged();
+ void toolTipFontChanged();
+ void tumblerFontChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitFont)
+
+ mutable std::unique_ptr<QFont> m_systemFont;
+ mutable std::unique_ptr<QFont> m_buttonFont;
+ mutable std::unique_ptr<QFont> m_checkboxFont;
+ mutable std::unique_ptr<QFont> m_comboBoxFont;
+ mutable std::unique_ptr<QFont> m_groupBoxFont;
+ mutable std::unique_ptr<QFont> m_itemViewFont;
+ mutable std::unique_ptr<QFont> m_labelFont;
+ mutable std::unique_ptr<QFont> m_listViewFont;
+ mutable std::unique_ptr<QFont> m_menuFont;
+ mutable std::unique_ptr<QFont> m_menuBarFont;
+ mutable std::unique_ptr<QFont> m_radioButtonFont;
+ mutable std::unique_ptr<QFont> m_spinBoxFont;
+ mutable std::unique_ptr<QFont> m_switchControlFont;
+ mutable std::unique_ptr<QFont> m_tabBarFont;
+ mutable std::unique_ptr<QFont> m_textAreaFont;
+ mutable std::unique_ptr<QFont> m_textFieldFont;
+ mutable std::unique_ptr<QFont> m_toolBarFont;
+ mutable std::unique_ptr<QFont> m_toolTipFont;
+ mutable std::unique_ptr<QFont> m_tumblerFont;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITFONT_H
diff --git a/src/labs/stylekit/qqstylekitglobal.cpp b/src/labs/stylekit/qqstylekitglobal.cpp
new file mode 100644
index 0000000000..481afa3cc6
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitglobal.cpp
@@ -0,0 +1,10 @@
+// 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
+
+#include "qqstylekitglobal_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitglobal_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitglobal_p.h b/src/labs/stylekit/qqstylekitglobal_p.h
new file mode 100644
index 0000000000..7ff2bf42ce
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitglobal_p.h
@@ -0,0 +1,154 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITGLOBAL_P_H
+#define QQSTYLEKITGLOBAL_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 <QtQml/QtQml>
+
+QT_BEGIN_NAMESPACE
+
+class QQSK: public QObject
+{
+ Q_OBJECT
+
+public:
+ enum class Delegate {
+ NoDelegate = 0x0000,
+ Control = 0x0001,
+ Background = 0x0002,
+ Handle = 0x0004,
+ HandleFirst = 0x0008,
+ HandleSecond = 0x0010,
+ Indicator = 0x0020,
+ IndicatorForeground = 0x0040,
+ IndicatorUp = 0x0080,
+ IndicatorUpForeground = 0x0100,
+ IndicatorDown = 0x0200,
+ IndicatorDownForeground = 0x0400,
+ };
+ Q_DECLARE_FLAGS(Delegates, Delegate)
+ Q_FLAG(Delegate)
+
+ enum class PropertyGroup {
+ NoGroup,
+ Control,
+ Background,
+ Border,
+ DelegateSubType1,
+ DelegateSubType2,
+ Handle,
+ Foreground,
+ Image,
+ Indicator,
+ Shadow,
+ globalFlag,
+ Text,
+ COUNT
+ };
+ Q_ENUM(PropertyGroup)
+
+ enum class Property {
+ NoProperty,
+ BottomLeftRadius,
+ BottomMargin,
+ BottomPadding,
+ BottomRightRadius,
+ Clip,
+ Color,
+ Data,
+ Delegate,
+ FillMode,
+ Gradient,
+ HOffset,
+ Image,
+ ImplicitWidth,
+ ImplicitHeight,
+ LeftMargin,
+ LeftPadding,
+ Margins,
+ MinimumWidth,
+ Opacity,
+ Padding,
+ Radius,
+ RightMargin,
+ RightPadding,
+ Rotation,
+ Scale,
+ Source,
+ Spacing,
+ TopLeftRadius,
+ TopMargin,
+ TopPadding,
+ TopRightRadius,
+ Transition,
+ Variations,
+ Visible,
+ VOffset,
+ Width,
+ Blur,
+ Alignment,
+ COUNT
+ };
+ Q_ENUM(Property)
+
+ enum class StateFlag {
+ NoState = 0x000,
+ Normal = 0x001,
+ Pressed = 0x002,
+ Hovered = 0x004,
+ Highlighted = 0x008,
+ Focused = 0x010,
+ Checked = 0x020,
+ Vertical = 0x040,
+ Disabled = 0x080,
+ MAX_STATE = 0x100,
+ };
+ Q_DECLARE_FLAGS(State, StateFlag)
+ Q_FLAG(State)
+
+ enum class Subclass {
+ QQStyleKitState,
+ QQStyleKitReader,
+ };
+ Q_ENUM(Subclass)
+
+ enum class PathFlag {
+ NoFlag = 0x00,
+ StyleDirect = 0x01
+ };
+ Q_DECLARE_FLAGS(PathFlags, PathFlag)
+ Q_FLAG(PathFlag)
+
+public:
+ template <typename T, typename Owner, typename... Args>
+ static inline T *lazyCreate(T *const &ptr, const Owner *self, Args&&... args)
+ {
+ if (!ptr) {
+ auto *mutableSelf = const_cast<Owner *>(self);
+ auto *&mutablePtr = const_cast<T *&>(ptr);
+ mutablePtr = new T(std::forward<Args>(args)..., mutableSelf);
+ }
+ return ptr;
+ }
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQSK::State)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQSK::Delegates)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QQSK::PathFlags)
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITGLOBAL_P_H
diff --git a/src/labs/stylekit/qqstylekitpalette.cpp b/src/labs/stylekit/qqstylekitpalette.cpp
new file mode 100644
index 0000000000..ec9e3b3f07
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitpalette.cpp
@@ -0,0 +1,42 @@
+// 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
+
+#include "qqstylekitpalette_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitPalette::QQStyleKitPalette(QObject *parent)
+ : QObject(parent)
+{
+}
+
+#define UNIFIED_PALETTE_GETTER(PROP) \
+QQuickPalette *QQStyleKitPalette::PROP() const { \
+ if (!m_ ## PROP) \
+ m_ ## PROP.reset(new QQuickPalette()); \
+ return m_ ## PROP.get(); \
+}
+
+UNIFIED_PALETTE_GETTER(system)
+UNIFIED_PALETTE_GETTER(checkBox)
+UNIFIED_PALETTE_GETTER(button)
+UNIFIED_PALETTE_GETTER(comboBox)
+UNIFIED_PALETTE_GETTER(groupBox)
+UNIFIED_PALETTE_GETTER(itemView)
+UNIFIED_PALETTE_GETTER(label)
+UNIFIED_PALETTE_GETTER(listView)
+UNIFIED_PALETTE_GETTER(menu)
+UNIFIED_PALETTE_GETTER(menuBar)
+UNIFIED_PALETTE_GETTER(radioButton)
+UNIFIED_PALETTE_GETTER(spinBox)
+UNIFIED_PALETTE_GETTER(switchControl)
+UNIFIED_PALETTE_GETTER(tabBar)
+UNIFIED_PALETTE_GETTER(textArea)
+UNIFIED_PALETTE_GETTER(textField)
+UNIFIED_PALETTE_GETTER(toolBar)
+UNIFIED_PALETTE_GETTER(toolTip)
+UNIFIED_PALETTE_GETTER(tumbler)
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitpalette_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitpalette_p.h b/src/labs/stylekit/qqstylekitpalette_p.h
new file mode 100644
index 0000000000..3f93ba982b
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitpalette_p.h
@@ -0,0 +1,124 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITPALETTE_P_H
+#define QQSTYLEKITPALETTE_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 <QtQml/QtQml>
+#include <QtQuick/private/qquickpalette_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitPalette : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickPalette *system READ system NOTIFY systemChanged FINAL)
+ Q_PROPERTY(QQuickPalette *checkBox READ checkBox NOTIFY checkBoxChanged FINAL)
+ Q_PROPERTY(QQuickPalette *button READ button NOTIFY buttonChanged FINAL)
+ Q_PROPERTY(QQuickPalette *comboBox READ comboBox NOTIFY comboBoxChanged FINAL)
+ Q_PROPERTY(QQuickPalette *groupBox READ groupBox NOTIFY groupBoxChanged FINAL)
+ Q_PROPERTY(QQuickPalette *itemView READ itemView NOTIFY itemViewChanged FINAL)
+ Q_PROPERTY(QQuickPalette *label READ label NOTIFY labelChanged FINAL)
+ Q_PROPERTY(QQuickPalette *listView READ listView NOTIFY listViewChanged FINAL)
+ Q_PROPERTY(QQuickPalette *menu READ menu NOTIFY menuChanged FINAL)
+ Q_PROPERTY(QQuickPalette *menuBar READ menuBar NOTIFY menuBarChanged FINAL)
+ Q_PROPERTY(QQuickPalette *radioButton READ radioButton NOTIFY radioButtonChanged FINAL)
+ Q_PROPERTY(QQuickPalette *spinBox READ spinBox NOTIFY spinBoxChanged FINAL)
+ Q_PROPERTY(QQuickPalette *switchControl READ switchControl NOTIFY switchControlChanged FINAL)
+ Q_PROPERTY(QQuickPalette *tabBar READ tabBar NOTIFY tabBarChanged FINAL)
+ Q_PROPERTY(QQuickPalette *textArea READ textArea NOTIFY textAreaChanged FINAL)
+ Q_PROPERTY(QQuickPalette *textField READ textField NOTIFY textFieldChanged FINAL)
+ Q_PROPERTY(QQuickPalette *toolBar READ toolBar NOTIFY toolBarChanged FINAL)
+ Q_PROPERTY(QQuickPalette *toolTip READ toolTip NOTIFY toolTipChanged FINAL)
+ Q_PROPERTY(QQuickPalette *tumbler READ tumbler NOTIFY tumblerChanged FINAL)
+
+ QML_NAMED_ELEMENT(StyleKitPalette)
+
+public:
+ QQStyleKitPalette(QObject *parent = nullptr);
+
+ QQuickPalette *system() const;
+ QQuickPalette *checkBox() const;
+ QQuickPalette *button() const;
+ QQuickPalette *comboBox() const;
+ QQuickPalette *groupBox() const;
+ QQuickPalette *itemView() const;
+ QQuickPalette *label() const;
+ QQuickPalette *listView() const;
+ QQuickPalette *menu() const;
+ QQuickPalette *menuBar() const;
+ QQuickPalette *radioButton() const;
+ QQuickPalette *spinBox() const;
+ QQuickPalette *switchControl() const;
+ QQuickPalette *tabBar() const;
+ QQuickPalette *textArea() const;
+ QQuickPalette *textField() const;
+ QQuickPalette *toolBar() const;
+ QQuickPalette *toolTip() const;
+ QQuickPalette *tumbler() const;
+
+signals:
+ void systemChanged();
+ void checkBoxChanged();
+ void buttonChanged();
+ void comboBoxChanged();
+ void groupBoxChanged();
+ void itemViewChanged();
+ void labelChanged();
+ void listViewChanged();
+ void menuChanged();
+ void menuBarChanged();
+ void radioButtonChanged();
+ void spinBoxChanged();
+ void switchControlChanged();
+ void tabBarChanged();
+ void textAreaChanged();
+ void textFieldChanged();
+ void toolBarChanged();
+ void toolTipChanged();
+ void tumblerChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitPalette)
+ /*
+ The following properties are lazy-created, since it's unlikely that a style
+ sets them all. And since a QQuickPalette is not a QObject, we use std::unique_ptr
+ for memory management. Since each palette is logically independent of this class,
+ we make them mutable so that the getter functions can be const.
+ */
+ mutable std::unique_ptr<QQuickPalette> m_system;
+ mutable std::unique_ptr<QQuickPalette> m_checkBox;
+ mutable std::unique_ptr<QQuickPalette> m_button;
+ mutable std::unique_ptr<QQuickPalette> m_comboBox;
+ mutable std::unique_ptr<QQuickPalette> m_groupBox;
+ mutable std::unique_ptr<QQuickPalette> m_itemView;
+ mutable std::unique_ptr<QQuickPalette> m_label;
+ mutable std::unique_ptr<QQuickPalette> m_listView;
+ mutable std::unique_ptr<QQuickPalette> m_menu;
+ mutable std::unique_ptr<QQuickPalette> m_menuBar;
+ mutable std::unique_ptr<QQuickPalette> m_radioButton;
+ mutable std::unique_ptr<QQuickPalette> m_spinBox;
+ mutable std::unique_ptr<QQuickPalette> m_switchControl;
+ mutable std::unique_ptr<QQuickPalette> m_tabBar;
+ mutable std::unique_ptr<QQuickPalette> m_textArea;
+ mutable std::unique_ptr<QQuickPalette> m_textField;
+ mutable std::unique_ptr<QQuickPalette> m_toolBar;
+ mutable std::unique_ptr<QQuickPalette> m_toolTip;
+ mutable std::unique_ptr<QQuickPalette> m_tumbler;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITPALETTE_P_H
diff --git a/src/labs/stylekit/qqstylekitpropertyresolver.cpp b/src/labs/stylekit/qqstylekitpropertyresolver.cpp
new file mode 100644
index 0000000000..211cdaba94
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitpropertyresolver.cpp
@@ -0,0 +1,674 @@
+// 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
+
+#include "qqstylekitpropertyresolver_p.h"
+#include "qqstylekitcontrol_p.h"
+#include "qqstylekitcontrols_p.h"
+#include "qqstylekitvariation_p.h"
+#include "qqstylekitstyle_p.h"
+#include "qqstylekittheme_p.h"
+#include "qqstylekitdebug_p.h"
+
+#include <QtQuickTemplates2/private/qquickcontrol_p.h>
+#include <QtCore/QScopedValueRollback>
+
+QT_BEGIN_NAMESPACE
+
+bool QQStyleKitPropertyResolver::s_styleWarningsIssued = false;
+bool QQStyleKitPropertyResolver::s_isReadingProperty = false;
+QQSK::State QQStyleKitPropertyResolver::s_cachedState = QQSK::StateFlag::NoState;
+QVarLengthArray<QQSK::StateFlag, 10> QQStyleKitPropertyResolver::s_cachedStateList;
+
+PropertyPathId QQStyleKitPropertyResolver::pathId(
+ const QQStyleKitPropertyGroup *group, const QQSK::Property property, PathId flag)
+{
+ /* Follow the parent chain of the property up to the QQStyleKitControlProperties
+ * group it's inside. This group path, together with the enum value of the property,
+ * will form its unique PropertyPathId.
+ * E.g the property 'color' will get a different path ID when it's a part of
+ * 'background.color' compared to 'background.border.color'.
+ * This path ID will later be used together with different state combinations to
+ * form different PropertyStorageId's. A storage ID is used as the key into QMaps
+ * that stores property values for each state in each QQStyleKitControl. */
+ if (property == QQSK::Property::NoProperty)
+ return PropertyPathId();
+
+ const int propertyCount = int(QQSK::Property::COUNT);
+ const int groupCount = int(QQSK::PropertyGroup::COUNT);
+
+ /* Deliberatly use extra wide types for calculations, in order
+ * to do rough overflow checks in the end. */
+ quint64 id = qint64(property);
+ quint64 idSpaceForPreviousLevel = propertyCount;
+
+ const QQStyleKitPropertyGroup *groupParent = group;
+
+ if (flag == PathId::ExcludeSubType && groupParent->isDelegateSubType())
+ groupParent = static_cast<QQStyleKitPropertyGroup *>(groupParent->parent());
+
+ if (groupParent->isPathFlag()) {
+ /* property 'global' is not a real group, it's just a hint to the property
+ * resolver that it should read style properties directly from the style, igoring
+ * any ongoing transition inside the reader. So just skip it. */
+ groupParent = static_cast<QQStyleKitPropertyGroup *>(groupParent->parent());
+ }
+
+ while (!groupParent->isControlProperties()) {
+ // Add 1 to the group number since group 0 (with start ID == 0) is
+ // reserved for properties that are not nested in a group (e.g control.implicitWidth).
+ const int groupNumber = int(groupParent->group()) + 1;
+ const quint64 idSpaceForCurrentGroup = groupNumber * idSpaceForPreviousLevel;
+
+ id += idSpaceForCurrentGroup;
+
+ /* Every time we move up one group level, all the possible property paths
+ * in the previous level can theoretically occur inside each group on this
+ * level. So we need to multiply this space with group count. */
+ idSpaceForPreviousLevel *= groupCount;
+
+ groupParent = static_cast<QQStyleKitPropertyGroup *>(groupParent->parent());
+ if (flag == PathId::ExcludeSubType && groupParent->isDelegateSubType()) {
+ groupParent = static_cast<QQStyleKitPropertyGroup *>(groupParent->parent());
+ Q_ASSERT(groupParent);
+ }
+ Q_ASSERT(groupParent);
+ if (groupParent->isPathFlag()) {
+ groupParent = static_cast<QQStyleKitPropertyGroup *>(groupParent->parent());
+ Q_ASSERT(groupParent);
+ }
+ }
+
+ const PropertyPathId pathId(property, id, idSpaceForPreviousLevel);
+
+#ifdef QT_DEBUG
+ // Check that the id calculation didn't overflow
+ Q_ASSERT_X(pathId.pathId() == id,
+ __FUNCTION__, QQStyleKitDebug::propertyPath(group, property).toUtf8().constData());
+
+ /* Also check in advance that the path ID can be used in combination with
+ * any possible state combination later on to form a storage ID. */
+ const QQSK::StateFlag maxNestedState = QQSK::StateFlag::MAX_STATE;
+ Q_ASSERT_X(id < pathId.storageId(maxNestedState),
+ __FUNCTION__, QQStyleKitDebug::propertyPath(group, property).toUtf8().constData());
+#endif
+
+ return pathId;
+}
+
+const QList<int> QQStyleKitPropertyResolver::baseTypesForType(int exactType)
+{
+ /* The base types should, by default, mirror the class hierarchy in Qt Quick Controls.
+ * Exceptions:
+ * ItemDelegate - is normally used as an menu item in a combo, or item in a ListView. It
+ * is in any case styled quite differently than a button. */
+ switch (exactType) {
+ case QQStyleKitReader::Button:
+ case QQStyleKitReader::RadioButton:
+ case QQStyleKitReader::CheckBox:
+ case QQStyleKitReader::SwitchControl: {
+ static QList<int> t = { QQStyleKitReader::AbstractButton, QQStyleKitReader::Control };
+ return t; }
+ case QQStyleKitReader::Menu:
+ case QQStyleKitReader::Dialog: {
+ static QList<int> t = { QQStyleKitReader::Popup, QQStyleKitReader::Control };
+ return t; }
+ case QQStyleKitReader::Page:
+ case QQStyleKitReader::Frame: {
+ static QList<int> t = { QQStyleKitReader::Pane, QQStyleKitReader::Control };
+ return t; }
+ case QQStyleKitReader::TextField:
+ case QQStyleKitReader::TextArea: {
+ static QList<int> t = { QQStyleKitReader::TextInput, QQStyleKitReader::Control };
+ return t; }
+ default: {
+ static QList<int> t = { QQStyleKitReader::Control };
+ return t; }
+ }
+
+ Q_UNREACHABLE();
+ return {};
+}
+
+void QQStyleKitPropertyResolver::cacheReaderState(QQSK::State state)
+{
+ if (state == s_cachedState)
+ return;
+
+ s_cachedState = state;
+
+ /* Note: The order in which we add the states below matters.
+ * The reason is that the s_cachedStateList that we build is used by QQStyleKitPropertyResolver
+ * later to generate all the different state combinations that should be tested when
+ * searching for a property. And the states added first to the list will "win" if the
+ * same property is set in several of the states. */
+ s_cachedStateList.clear();
+ if (state.testFlag(QQSK::StateFlag::Pressed))
+ s_cachedStateList.append(QQSK::StateFlag::Pressed);
+ if (state.testFlag(QQSK::StateFlag::Hovered))
+ s_cachedStateList.append(QQSK::StateFlag::Hovered);
+ if (state.testFlag(QQSK::StateFlag::Highlighted))
+ s_cachedStateList.append(QQSK::StateFlag::Highlighted);
+ if (state.testFlag(QQSK::StateFlag::Focused))
+ s_cachedStateList.append(QQSK::StateFlag::Focused);
+ if (state.testFlag(QQSK::StateFlag::Checked))
+ s_cachedStateList.append(QQSK::StateFlag::Checked);
+ if (state.testFlag(QQSK::StateFlag::Vertical))
+ s_cachedStateList.append(QQSK::StateFlag::Vertical);
+ if (state.testFlag(QQSK::StateFlag::Disabled))
+ s_cachedStateList.append(QQSK::StateFlag::Disabled);
+}
+
+void QQStyleKitPropertyResolver::rebuildParentChainForReader(QQStyleKitReader *styleReader)
+{
+ /* Build up the parent chain of readers from \a styleReader. If we encounter a
+ * reader that already has m_parentChainDirty set to false, we can return early. This
+ * means that the execution of this function will go faster and faster for each call. */
+ styleReader->m_parentChainDirty = false;
+ QQStyleKitReader *childReader = styleReader;
+ QObject *parentObj = styleReader->parent();
+ QQuickItem *parentItem = nullptr;
+
+ while (parentObj) {
+ // TODO: change code to look for attached prop instead of styleReader prop?
+ parentItem = qobject_cast<QQuickItem *>(parentObj);
+ if (parentItem) {
+ const QVariant readerAsVariant = parentItem->property("styleReader");
+ if (readerAsVariant.isValid()) {
+ if (auto *reader = qvariant_cast<QQStyleKitReader *>(readerAsVariant)) {
+ /* Whether the reader has a parent reader or not (which we find out in
+ * the next iteration), we now anyway mark it as resolved. */
+ reader->m_parentChainDirty = false;
+ /* Some controls use several style readers (e.g RangeSlider), which are
+ * typically siblings of each other. So ensure we don't make a sibling
+ * reader the parent of another sibling reader. */
+ if (reader->parent() != childReader->parent())
+ childReader->m_parentReader = reader;
+ if (reader->m_parentReader)
+ break;
+
+ childReader = reader;
+ }
+ }
+ parentItem = parentItem->parentItem();
+ }
+ parentObj = parentItem ? parentItem : parentObj->parent();
+ }
+}
+
+void QQStyleKitPropertyResolver::rebuildVariationsForReader(
+ QQStyleKitReader *styleReader, const QQStyleKitStyle *style)
+{
+ Q_ASSERT(styleReader->m_effectiveVariationsDirty);
+ styleReader->m_effectiveVariationsDirty = false;
+
+ if (styleReader->m_parentChainDirty)
+ rebuildParentChainForReader(styleReader);
+
+ // We make the id's static, since they shouldn't change depending on the styleReader
+ static PropertyPathIds ids;
+ if (ids.property.property() == QQSK::Property::NoProperty) {
+ ids.property = pathId(styleReader, QQSK::Property::Variations, PathId::IncludeSubType);
+ ids.alternative = pathId(styleReader, QQSK::Property::NoProperty, PathId::IncludeSubType);
+ ids.subTypeProperty = PropertyPathId();
+ ids.subTypeAlternative = PropertyPathId();
+ }
+
+ /* Go through all the parent readers of \a styleReader (if any), and for each one,
+ * add the variations attached to them that has the potential to affect the
+ * type/control represented by styleReader to m_effectiveVariations. The variations
+ * that are closest to styleReader in the hierarchy will be added first and take
+ * precendence over the ones added last. */
+ styleReader->m_effectiveVariations.clear();
+ QList<int> affectedTypes = baseTypesForType(styleReader->type());
+ affectedTypes.prepend(styleReader->type());
+ QQStyleKitReader *parentReader = styleReader->m_parentReader;
+ QQStyleKitReader *lastParentWithVariations = nullptr;
+
+ while (parentReader) {
+ if (!parentReader->m_hierarchyHasVariations)
+ break;
+
+ /* Note: when we read the variations property from the style, it returns the varations
+ * set in the most specific storage/state/control (because of propagation). And those
+ * are the only ones that will take effect. This means that even if there are variations
+ * in the fallback style for the requested type (control) that overrides some properties,
+ * and the style/theme has variations that overrides something else, the variations in
+ * the fallback style will, in that case, be ignored for that type. The properties _not
+ * set_ in the effective variations will instead propagate back to be read from the
+ * type in the theme or style, like all other properties. */
+ const int parentType = parentReader->type();
+ const QList<int> baseTypes = baseTypesForType(parentType);
+ const QVariant variationVariant = readPropertyInStyle(
+ ids, parentType, baseTypes, parentReader, style);
+
+ if (variationVariant.isValid()) {
+ auto variationsInStyle = *qvariant_cast<QList<QQStyleKitVariation *> *>(variationVariant);
+ for (auto *variation : std::as_const(variationsInStyle)) {
+ if (!variation) {
+ // Ignore unsupported elements (text, numbers, etc), added to the array from QML
+ continue;
+ }
+
+ lastParentWithVariations = parentReader;
+
+ for (int type : affectedTypes) {
+ if (variation->getControl(type)) {
+ styleReader->m_effectiveVariations.append(variation);
+ // qDebug() << "Found variation" << variation
+ // << "for" << styleReader->typeAsControlType()
+ // << "in" << parentReader->typeAsControlType();
+ break;
+ }
+ }
+ }
+ }
+
+ parentReader = parentReader->m_parentReader;
+ }
+
+ if (lastParentWithVariations) {
+ /* This function will be called for all style readers / controls after a style or theme
+ * change. So as an optimization, we do an extra pass in the end where we mark all the
+ * readers we found above from the parent of lastParentWithVariations and up to the root
+ * as not having variations. This allows us to return early for all the remaining style
+ * readers that shares the same, or parts of the same, parent chain. */
+ QQStyleKitReader *parentReader = lastParentWithVariations->m_parentReader;
+ while (parentReader) {
+ if (!parentReader->m_hierarchyHasVariations)
+ break;
+ parentReader->m_hierarchyHasVariations = false;
+ parentReader = parentReader->m_parentReader;
+ }
+ }
+}
+
+template <class T>
+QVariant QQStyleKitPropertyResolver::readPropertyInStorageForState(
+ const PropertyPathId main, const PropertyPathId alternative,
+ const T *storageProvider, QQSK::State state)
+{
+ /* If the propertyId or altPropertyId is set in the control storage, we've found the best
+ * match, and can return the value back to the application. The reason we need an altPropertyId,
+ * is because some properties can be set more that one way. For example 'topLeftRadius' can be
+ * set using either 'topLeftRadius' (main) or 'radius' (alternative). The first found in the
+ * propagation chain wins. This means that when resolving 'topLeftRadius' for a button, if
+ * 'radius' is set in 'button', and 'topLeftRadius' is set in abstractButton, 'radius' will
+ * override 'topLeftRadius', and win. */
+ Q_ASSERT(qlonglong(state) <= qlonglong(QQSK::StateFlag::MAX_STATE));
+
+ const PropertyStorageId propertyKey = main.storageId(state);
+
+ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
+ QQStyleKitDebug::trace(main, storageProvider, state, propertyKey);
+
+ const QVariant propertyValue = storageProvider->readStyleProperty(propertyKey);
+ if (propertyValue.isValid()) {
+ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
+ QQStyleKitDebug::notifyPropertyRead(main, storageProvider, state, propertyValue);
+ return propertyValue;
+ }
+
+ const PropertyStorageId altPropertyKey = alternative.storageId(state);
+
+ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
+ QQStyleKitDebug::trace(alternative, storageProvider, state, altPropertyKey);
+
+ const QVariant altValue = storageProvider->readStyleProperty(altPropertyKey);
+ if (altValue.isValid()) {
+ if (Q_UNLIKELY(QQStyleKitDebug::enabled()))
+ QQStyleKitDebug::notifyPropertyRead(main, storageProvider, state, altValue);
+ return altValue;
+ }
+
+ return {};
+}
+
+template <class INDICES_CONTAINER>
+QVariant QQStyleKitPropertyResolver::readPropertyInControlForStates(
+ const PropertyPathId main, const PropertyPathId alternative,
+ const QQStyleKitControl *control, INDICES_CONTAINER &stateListIndices,
+ int startIndex, int recursionLevel)
+{
+ for (int i = startIndex; i < s_cachedStateList.length(); ++i) {
+ /* stateListIndices is a helper list to track which index in the state list
+ * each recursion level is currently processing. The first recursion level
+ * will iterate through all of the states. The second recursion level will
+ * only the iterate through the states that comes after the state at the
+ * previous recursion. And so on recursively. The end result will be a list of
+ * state combinations (depth first) where no state is repeated more than
+ * once. And for each combination, we check if the storage has a value assigned
+ * for the given property for the given state combination. */
+ stateListIndices[recursionLevel] = i;
+ const QQSK::StateFlag stateFlag = s_cachedStateList[i];
+
+ if (!control->m_writtenStates.testFlag(stateFlag)) {
+ /* optimization: the control doesn't have any properties for the state
+ * we're processing. So continue to the next state in the list. */
+ continue;
+ }
+
+ if (recursionLevel < s_cachedStateList.length() - 1) {
+ // Continue the recursion towards the longest possible nested state
+ const QVariant value = readPropertyInControlForStates(
+ main, alternative, control, stateListIndices, i + 1, recursionLevel + 1);
+ if (value.isValid())
+ return value;
+ }
+
+ // Check the current combination
+ QQSK::State storageState = QQSK::StateFlag::NoState;
+ for (int j = 0; j <= recursionLevel; ++j)
+ storageState.setFlag(s_cachedStateList[stateListIndices[j]]);
+ const QVariant value = readPropertyInStorageForState(main, alternative, control, storageState);
+ if (value.isValid())
+ return value;
+ }
+
+ return {};
+}
+
+QVariant QQStyleKitPropertyResolver::readPropertyInControl(
+ const PropertyPathIds &ids, const QQStyleKitControl *control)
+{
+ /* Find the most specific state combination (based on the state of the reader) that
+ * has a value set for the property in the contol. In case several state combinations
+ * could be found, the order of the states in the stateList decides the priority.
+ * If we're reading a property in a sub type, try all state combinations in the sub
+ * type first, before trying all the state combinations in the super type. */
+ QVarLengthArray<int, 10> stateListIndices(s_cachedStateList.length());
+
+ if (ids.subTypeProperty.property() != QQSK::Property::NoProperty) {
+ if (s_cachedState != QQSK::StateFlag::NoState) {
+ QVariant value = readPropertyInControlForStates(
+ ids.subTypeProperty, ids.subTypeAlternative, control, stateListIndices, 0, 0);
+ if (value.isValid())
+ return value;
+ }
+
+ if (control->m_writtenStates.testFlag(QQSK::StateFlag::Normal)) {
+ const QVariant value = readPropertyInStorageForState(
+ ids.subTypeProperty, ids.subTypeAlternative, control, QQSK::StateFlag::Normal);
+ if (value.isValid())
+ return value;
+ }
+ }
+
+ if (s_cachedState != QQSK::StateFlag::NoState) {
+ const QVariant value = readPropertyInControlForStates(
+ ids.property, ids.alternative, control, stateListIndices, 0, 0);
+ if (value.isValid())
+ return value;
+ }
+
+ /* The normal state is the propagation fall back for all state combinations.
+ * If the normal state has the property set, it'll return a valid QVariant,
+ * which will cause the propagation to stop. Otherwise we'll return an invalid
+ * variant which will cause the search to continue. */
+ if (control->m_writtenStates.testFlag(QQSK::StateFlag::Normal))
+ return readPropertyInStorageForState(ids.property, ids.alternative, control, QQSK::StateFlag::Normal);
+
+ return {};
+}
+
+QVariant QQStyleKitPropertyResolver::readPropertyInRelevantControls(
+ const QQStyleKitControls *controls, const PropertyPathIds &ids,
+ const int exactType, const QList<int> baseTypes)
+{
+ if (!controls)
+ return {};
+
+ if (const QQStyleKitControl *control = controls->getControl(exactType)) {
+ const QVariant value = readPropertyInControl(ids, control);
+ if (value.isValid())
+ return value;
+ }
+
+ for (const int type : baseTypes) {
+ if (const QQStyleKitControl *control = controls->getControl(type)) {
+ const QVariant value = readPropertyInControl(ids, control);
+ if (value.isValid())
+ return value;
+ }
+ }
+
+ return {};
+}
+
+QVariant QQStyleKitPropertyResolver::readPropertyInStyle(
+ const PropertyPathIds &ids, const int exactType, const QList<int> baseTypes,
+ const QQStyleKitReader *styleReader, const QQStyleKitStyle *style)
+{
+ QVariant value;
+
+ while (true) {
+ value = readPropertyInRelevantControls(style->theme(), ids, exactType, baseTypes);
+ if (value.isValid())
+ break;
+ value = readPropertyInRelevantControls(style, ids, exactType, baseTypes);
+ if (value.isValid())
+ break;
+
+ if (auto *fallbackStyle = style->fallbackStyle()) {
+ /* Recurse into the fallback style, and search for the property there. If not
+ * found, and the fallback style has a fallback style, the recursion continues. */
+ fallbackStyle->setPalette(styleReader->palette());
+ value = readPropertyInStyle(ids, exactType, baseTypes, styleReader, fallbackStyle);
+ if (value.isValid())
+ break;
+ }
+
+ break;
+ }
+
+ if (Q_UNLIKELY(QQStyleKitDebug::enabled())) {
+ if (!value.isValid())
+ QQStyleKitDebug::notifyPropertyNotResolved(ids.property);
+ }
+
+ return value;
+}
+
+QVariant QQStyleKitPropertyResolver::readProperty(
+ const PropertyPathIds &ids, QQStyleKitReader *styleReader, const QQStyleKitStyle *style)
+{
+ if (styleReader->m_effectiveVariationsDirty)
+ rebuildVariationsForReader(styleReader, style);
+
+ const int exactType = styleReader->type();
+ const QList<int> baseTypes = baseTypesForType(exactType);
+
+ QVariant value;
+
+ while (true) {
+ for (const auto *variation : styleReader->m_effectiveVariations) {
+ value = readPropertyInRelevantControls(variation, ids, exactType, baseTypes);
+ if (value.isValid())
+ break;
+ }
+ if (value.isValid())
+ break;
+
+ value = readPropertyInStyle(ids, exactType, baseTypes, styleReader, style);
+ break;
+ }
+
+ if (Q_UNLIKELY(QQStyleKitDebug::enabled())) {
+ if (!value.isValid())
+ QQStyleKitDebug::notifyPropertyNotResolved(ids.property);
+ }
+
+ return value;
+}
+
+QVariant QQStyleKitPropertyResolver::readStyleProperty(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property,
+ const QQSK::Property alternative)
+{
+ auto [controlProperties, subType, pathFlags] = group->inspectGroupPath();
+ const QQSK::Subclass subclass = controlProperties->subclass();
+
+ if (subclass == QQSK::Subclass::QQStyleKitState) {
+ /* Because of propagation, we need to know the state of the reader in order to
+ * resolve the value for the given property. E.g the color of a background delegate
+ * will take a difference search path, and end up with a different value, depending
+ * of if the reader is a Slider or a Button, and if it's hovered or pressed etc.
+ * Therefore, when a property is instead accessed from a control (or a state in a
+ * control) in the UnifedStyle, we cannot support propagation. But to still allow
+ * static (as in non-propagating) bindings between the properties in a Style,
+ * we fall back to simply return the value specified in the accessed control. */
+ const auto [control, nestedState] = controlProperties->asQQStyleKitState()->controlAndState();
+ const PropertyPathId propertyPathId = pathId(group, property, PathId::IncludeSubType);
+ const PropertyStorageId key = propertyPathId.storageId(nestedState);
+ return control->readStyleProperty(key);
+ }
+
+ QQStyleKitStyle *style = controlProperties->style();
+ if (!style) {
+ if (!s_styleWarningsIssued) {
+ s_styleWarningsIssued = true;
+ qmlWarning(group) << "style properties cannot be read: No StyleKit style has been set!";
+ }
+ return {};
+ }
+
+ if (!style->loaded()) {
+ // Optimization: Skip reads until both the style and the theme is ready
+ return {};
+ }
+
+ if (subclass == QQSK::Subclass::QQStyleKitReader) {
+ QQStyleKitDebug::groupBeingRead = group;
+ QQStyleKitReader *styleReader = controlProperties->asQQStyleKitReader();
+
+ if (s_isReadingProperty) {
+ if (!s_styleWarningsIssued) {
+ s_styleWarningsIssued = true;
+ qmlWarning(styleReader) << "The style property '" << property << "' was read "
+ << "before finishing the read of another style property. "
+ << "This is likely to cause a style glitch.";
+ }
+ }
+ QScopedValueRollback rollback(s_isReadingProperty, true);
+
+ PropertyPathIds ids;
+ ids.property = pathId(group, property, PathId::ExcludeSubType);
+ ids.alternative = pathId(group, alternative, PathId::ExcludeSubType);
+
+ if (subType != QQSK::PropertyGroup::NoGroup) {
+ ids.subTypeProperty = pathId(group, property, PathId::IncludeSubType);
+ ids.subTypeAlternative = pathId(group, alternative, PathId::IncludeSubType);
+ } else {
+ ids.subTypeProperty = PropertyPathId();
+ ids.subTypeAlternative = PropertyPathId();
+ }
+
+ if (!pathFlags.testFlag(QQSK::PathFlag::StyleDirect)) {
+ /* A style reader can have a storage that contains local property overrides (that is,
+ * interpolated values from an ongoing transition). When searching for a property, we
+ * therefore need to check this storage first. The exception is if the property was read
+ * inside the 'global' group, which means that we should read the values directly
+ * from the style. */
+ if (subType != QQSK::PropertyGroup::NoGroup) {
+ const QVariant value = readPropertyInStorageForState(
+ ids.subTypeProperty, ids.subTypeAlternative, styleReader, QQSK::StateFlag::Normal);
+ if (value.isValid())
+ return value;
+ }
+ const QVariant value = readPropertyInStorageForState(
+ ids.property, ids.alternative, styleReader, QQSK::StateFlag::Normal);
+ if (value.isValid())
+ return value;
+ }
+
+ style->setPalette(styleReader->palette());
+ cacheReaderState(styleReader->controlState());
+ const QVariant value = readProperty(ids, styleReader, style);
+ return value;
+ }
+
+ Q_UNREACHABLE();
+ return {};
+}
+
+bool QQStyleKitPropertyResolver::writeStyleProperty(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property,
+ const QVariant &value)
+{
+ // While readStyleProperty() takes propagation into account, writeStyleProperty() doesn't.
+ // Instead it writes \a value directly to the storage that the group belongs to.
+ Q_ASSERT(group);
+ auto [controlProperties, subType, pathFlags] = group->inspectGroupPath();
+ const QQSK::Subclass subclass = controlProperties->subclass();
+ const PropertyPathId propertyPathId = pathId(group, property, PathId::IncludeSubType);
+ // The subType is only used when reading a property using propagation
+ Q_UNUSED(subType);
+
+ if (pathFlags.testFlag(QQSK::PathFlag::StyleDirect)) {
+ qmlWarning(controlProperties) << "Properties inside 'global' are read-only!";
+ return false;
+ }
+
+ if (subclass == QQSK::Subclass::QQStyleKitReader) {
+ // This is a write to a StyleKitReader, probably from an ongoing transition
+ QQStyleKitReader *reader = controlProperties->asQQStyleKitReader();
+ const PropertyStorageId key = propertyPathId.storageId(QQSK::StateFlag::Normal);
+ const QVariant currentValue = reader->readStyleProperty(key);
+ const bool valueChanged = currentValue != value;
+ if (valueChanged) {
+ reader->writeStyleProperty(key, value);
+ QQStyleKitDebug::notifyPropertyWrite(group, property, reader, QQSK::StateFlag::Normal, key, value);
+ }
+ return valueChanged;
+ }
+
+ if (subclass == QQSK::Subclass::QQStyleKitState) {
+ // This is a write to a control inside the StyleKit style
+ const auto [control, nestedState] = controlProperties->asQQStyleKitState()->controlAndState();
+ const PropertyStorageId key = propertyPathId.storageId(nestedState);
+ const QVariant currentValue = control->readStyleProperty(key);
+ const bool valueChanged = currentValue != value;
+ if (valueChanged) {
+ control->m_writtenStates |= nestedState;
+ control->writeStyleProperty(key, value);
+ QQStyleKitDebug::notifyPropertyWrite(group, property, control, nestedState, key, value);
+ }
+ return valueChanged;
+ }
+
+ Q_UNREACHABLE();
+ return false;
+}
+
+bool QQStyleKitPropertyResolver::hasLocalStyleProperty(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property)
+{
+ Q_ASSERT(group);
+ auto [controlProperties, subType, pathFlags] = group->inspectGroupPath();
+ const QQSK::Subclass subclass = controlProperties->subclass();
+ const PropertyPathId propertyPathId = pathId(group, property, PathId::IncludeSubType);
+ Q_UNUSED(subType);
+
+ if (pathFlags.testFlag(QQSK::PathFlag::StyleDirect))
+ return false;
+
+ if (subclass == QQSK::Subclass::QQStyleKitReader) {
+ const PropertyStorageId key = propertyPathId.storageId(QQSK::StateFlag::Normal);
+ return controlProperties->asQQStyleKitReader()->readStyleProperty(key).isValid();
+ }
+
+ if (subclass == QQSK::Subclass::QQStyleKitState) {
+ const auto [control, nestedState] = controlProperties->asQQStyleKitState()->controlAndState();
+ const PropertyStorageId key = propertyPathId.storageId(nestedState);
+ return control->readStyleProperty(key).isValid();
+ }
+
+ Q_UNREACHABLE();
+ return false;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitpropertyresolver_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitpropertyresolver_p.h b/src/labs/stylekit/qqstylekitpropertyresolver_p.h
new file mode 100644
index 0000000000..fc88a843e7
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitpropertyresolver_p.h
@@ -0,0 +1,114 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITPROPERTYRESOLVER_P_H
+#define QQSTYLEKITPROPERTYRESOLVER_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 <QtQml/QtQml>
+
+#include "qqstylekitglobal_p.h"
+#include "qqstylekitstorage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitControl;
+class QQStyleKitControls;
+class QQStyleKitStyle;
+class QQStyleKitReader;
+class QQStyleKitPropertyGroup;
+
+class QQStyleKitPropertyResolver
+{
+ Q_GADGET
+
+public:
+ enum class PathId {
+ ExcludeSubType,
+ IncludeSubType
+ };
+ Q_ENUM(PathId)
+
+ struct PropertyPathIds {
+ PropertyPathId property;
+ PropertyPathId alternative;
+ PropertyPathId subTypeProperty;
+ PropertyPathId subTypeAlternative;
+ };
+
+ static QVariant readStyleProperty(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property,
+ const QQSK::Property alternative = QQSK::Property::NoProperty);
+
+ static bool writeStyleProperty(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property,
+ const QVariant &value);
+
+ static bool hasLocalStyleProperty(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property);
+
+private:
+ static bool s_styleWarningsIssued;
+ static bool s_isReadingProperty;
+ static QQSK::State s_cachedState;
+ static QVarLengthArray<QQSK::StateFlag, 10> s_cachedStateList;
+
+private:
+ template <class T>
+ static QVariant readPropertyInStorageForState(
+ const PropertyPathId main, const PropertyPathId alternative,
+ const T *storageProvider, QQSK::State state);
+
+ template <class INDICES_CONTAINER>
+ static QVariant readPropertyInControlForStates(
+ const PropertyPathId main, const PropertyPathId alternative,
+ const QQStyleKitControl *control, INDICES_CONTAINER &stateListIndices,
+ int startIndex, int recursionLevel);
+
+ static QVariant readPropertyInControl(
+ const PropertyPathIds &ids, const QQStyleKitControl *control);
+
+ static QVariant readPropertyInRelevantControls(
+ const QQStyleKitControls *controls, const PropertyPathIds &ids,
+ const int exactType, const QList<int> baseTypes);
+
+ static QVariant readPropertyInStyle(
+ const PropertyPathIds &ids, const int exactType,
+ const QList<int> baseTypes, const QQStyleKitReader *styleReader,
+ const QQStyleKitStyle *style);
+
+ static QVariant readProperty(
+ const PropertyPathIds &ids,
+ QQStyleKitReader *styleReader,
+ const QQStyleKitStyle *style);
+
+ static PropertyPathId pathId(
+ const QQStyleKitPropertyGroup *group,
+ const QQSK::Property property, PathId flag);
+
+ static const QList<int> baseTypesForType(int exactType);
+
+ static void cacheReaderState(QQSK::State state);
+ static void rebuildParentChainForReader(QQStyleKitReader *styleReader);
+ static void rebuildVariationsForReader(
+ QQStyleKitReader *styleReader,
+ const QQStyleKitStyle *style);
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITPROPERTYRESOLVER_P_H
diff --git a/src/labs/stylekit/qqstylekitreader.cpp b/src/labs/stylekit/qqstylekitreader.cpp
new file mode 100644
index 0000000000..eb5ecf76bf
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitreader.cpp
@@ -0,0 +1,526 @@
+// 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
+
+#include <QtQuick/private/qquickpalette_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+#include <QtQuick/private/qquickstategroup_p.h>
+#include <QtQuick/private/qquickpropertychanges_p.h>
+
+#include "qqstylekit_p.h"
+#include "qqstylekitreader_p.h"
+#include "qqstylekitpropertyresolver_p.h"
+#include "qqstylekitstyle_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static const QString kAlternate1 = "A1"_L1;
+static const QString kAlternate2 = "A2"_L1;
+
+QList<QQStyleKitReader *> QQStyleKitReader::s_allReaders;
+QMap<QString, QQmlComponent *> QQStyleKitReader::s_propertyChangesComponents;
+
+QQStyleKitReader::QQStyleKitReader(QObject *parent)
+ : QQStyleKitControlProperties(QQSK::PropertyGroup::Control, parent)
+ , m_dontEmitChangedSignals(false)
+ , m_effectiveVariationsDirty(true)
+ , m_parentChainDirty(true)
+ , m_hierarchyHasVariations(true)
+ , m_global(QQStyleKitControlProperties(QQSK::PropertyGroup::globalFlag, this))
+{
+ s_allReaders.append(this);
+}
+
+QQStyleKitReader::~QQStyleKitReader()
+{
+ s_allReaders.removeOne(this);
+}
+
+QQuickStateGroup *QQStyleKitReader::stateGroup()
+{
+ if (m_stateGroup)
+ return m_stateGroup;
+
+ /* Lazy create a StyleKitReaderStateGroup as soon as we have delegates
+ * that needs to be "tracked". That is, the user of this StyleKitReader
+ * has read one or more properties, so we need to check and emit changes for
+ * those properties whenever our state changes. */
+ const auto *stylePtr = style();
+ Q_ASSERT(stylePtr);
+
+ m_stateGroup = new QQuickStateGroup(this);
+
+ // Add two states that we can alternate between
+ auto statesProp = m_stateGroup->statesProperty();
+ QQuickState *alternate1 = new QQuickState(m_stateGroup);
+ QQuickState *alternate2 = new QQuickState(m_stateGroup);
+ alternate1->setName(kAlternate1);
+ alternate2->setName(kAlternate2);
+ m_stateGroup->statesProperty().append(&statesProp, alternate1);
+ m_stateGroup->statesProperty().append(&statesProp, alternate2);
+
+ QQmlComponent *controlComp = createControlChangesComponent();
+ instantiatePropertyChanges(controlComp);
+
+ return m_stateGroup;
+}
+
+QQmlComponent *QQStyleKitReader::createControlChangesComponent() const
+{
+ static const QLatin1String propertyName("control"_L1);
+ if (s_propertyChangesComponents.contains(propertyName))
+ return s_propertyChangesComponents.value(propertyName);
+
+ const QString qmlControlCode = QString::fromUtf8(R"(
+ import QtQuick
+ PropertyChanges {
+ spacing: global.spacing
+ padding: global.padding
+ leftPadding: global.leftPadding
+ rightPadding: global.rightPadding
+ topPadding: global.topPadding
+ bottomPadding: global.bottomPadding
+ text.color: global.text.color
+ text.alignment: global.text.alignment
+ }
+ )");
+
+ // TODO: cache propertyName to component!
+ QQmlComponent *component = new QQmlComponent(qmlEngine(style()));
+ component->setData(qmlControlCode.toUtf8(), QUrl());
+ Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
+ s_propertyChangesComponents.insert(propertyName, component);
+ return component;
+}
+
+QQmlComponent *QQStyleKitReader::createDelegateChangesComponent(const QString &delegateName) const
+{
+ if (s_propertyChangesComponents.contains(delegateName))
+ return s_propertyChangesComponents.value(delegateName);
+
+ static const QString qmlTemplateCode = QString::fromUtf8(R"(
+ import QtQuick
+ PropertyChanges { $ {
+ implicitWidth: global.$.implicitWidth
+ implicitHeight: global.$.implicitHeight
+ visible: global.$.visible
+ color: global.$.color
+ gradient: global.$.gradient
+ radius: global.$.radius
+ topLeftRadius: global.$.topLeftRadius
+ topRightRadius: global.$.topRightRadius
+ bottomLeftRadius: global.$.bottomLeftRadius
+ bottomRightRadius: global.$.bottomRightRadius
+ margins: global.$.margins
+ alignment: global.$.alignment
+ leftMargin: global.$.leftMargin
+ rightMargin: global.$.rightMargin
+ topMargin: global.$.topMargin
+ bottomMargin: global.$.bottomMargin
+ scale: global.$.scale
+ rotation: global.$.rotation
+ opacity: global.$.opacity
+ border.color: global.$.border.color
+ border.width: global.$.border.width
+ shadow.color: global.$.shadow.color
+ shadow.scale: global.$.shadow.scale
+ shadow.blur: global.$.shadow.blur
+ shadow.visible: global.$.shadow.visible
+ shadow.opacity: global.$.shadow.opacity
+ shadow.verticalOffset: global.$.shadow.verticalOffset
+ shadow.horizontalOffset: global.$.shadow.horizontalOffset
+ shadow.delegate: global.$.shadow.delegate
+ image.source: global.$.image.source
+ image.color: global.$.image.color
+ image.fillMode: global.$.image.fillMode
+ delegate: global.$.delegate
+ data: global.$.data
+ }}
+ )");
+
+ QString substitutedCode = qmlTemplateCode;
+ substitutedCode.replace('$'_L1, delegateName);
+ QQmlComponent *component = new QQmlComponent(qmlEngine(style()));
+ component->setData(substitutedCode.toUtf8(), QUrl());
+ Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
+ s_propertyChangesComponents.insert(delegateName, component);
+ return component;
+}
+
+void QQStyleKitReader::instantiatePropertyChanges(QQmlComponent *comp)
+{
+ QObject *obj = comp->create(qmlContext(this));
+ auto *propertyChanges = qobject_cast<QQuickPropertyChanges *>(obj);
+ Q_ASSERT(propertyChanges);
+
+ // setter for the "target" property is called setObject
+ propertyChanges->setObject(this);
+ /* set "explicit" to true, meaning that the StyleProperties shouldn't
+ * create bindings, but do one-off assignments. Bindings are not needed
+ * here since it's the state changes of this StyleKitReader that
+ * drives the property changes. The properties cannot change outside of
+ * a state change (or, if they do, it will trigger a full update equal
+ * to a theme change) */
+ propertyChanges->setIsExplicit(true);
+ /* We don't need to ever restore the properties back to default, since
+ * the group state will never be reset back to an empty string. This will
+ * hopefully avoid the generation of restore structures inside the StateGroup. */
+ propertyChanges->setRestoreEntryValues(false);
+
+ /* Add the new PropertyChanges object to both states, A1 and A2 */
+ for (QQuickState *state : stateGroup()->states()) {
+ auto changesProp = state->changes();
+ changesProp.append(&changesProp, propertyChanges);
+ }
+}
+
+void QQStyleKitReader::maybeTrackDelegates()
+{
+ forEachUsedDelegate(
+ [this](QQStyleKitDelegateProperties *delegate, QQSK::Delegate type, const QString &delegatePath){
+ if (m_trackedDelegates.testFlag(type)) {
+ // We're already tracking the delegate. So nothing needs to be done.
+ return;
+ }
+ if (!delegate->visible()) {
+ /* As an optimization, if the delegate is hidden, we don't track it. Most
+ * controls set background.visible to false, for example. If this, for
+ * whatever reason, is not wanted, set opacity to 0 instead. */
+ return;
+ }
+ /* Invariant: The application has read one or more properties for the given delegate
+ * from the Style, but we don't yet have a PropertyChanges object that can track
+ * changes to it (and run transitions). So we create one now. By lazy creating them this
+ * way, we avoid creating PropertyChanges for all the different delegates that a control
+ * _may_ have. Instead, we only track changes for the delegates it actually uses. */
+ m_trackedDelegates.setFlag(type);
+ QQmlComponent *comp = createDelegateChangesComponent(delegatePath);
+ instantiatePropertyChanges(comp);
+ });
+}
+
+void QQStyleKitReader::updateControl()
+{
+ const QQStyleKitStyle *style = QQStyleKitStyle::current();
+ if (!style || !style->loaded())
+ return;
+
+ /* Alternate between two states to trigger a state change. The state group
+ * will, upon changing state, take care of reading the updated property values,
+ * compare them against the current ones in the local storage, and emit changes
+ * (possibly using a transition) if changed. Since the new state might change the
+ * transition, we need to update it first before we do the state change, so that
+ * it takes effect.
+ * If we have skipped tracking some delegates because they are hidden, we need to
+ * check again if this is still the case for the current state. Otherwise, we now
+ * need to track them. Untracked delegates are not backed by PropertyChanges objects,
+ * and hence, will not update when we do a state swap below.
+ * Note that the first time this function is called after start-up, none of the
+ * delegates are yet tracked, and therefore will be created now. */
+
+ maybeTrackDelegates();
+
+ auto transitionProp = stateGroup()->transitionsProperty();
+ const int transitionCountInStateGroup = transitionProp.count(&transitionProp);
+ const bool enabled = QQStyleKit::qmlAttachedProperties()->transitionsEnabled();
+ QQuickTransition *transitionInStyle = enabled ? transition() : nullptr;
+ QQuickTransition *transitionInStateGroup =
+ transitionCountInStateGroup > 0 ? transitionProp.at(&transitionProp, 0) : nullptr;
+ if (transitionInStyle != transitionInStateGroup) {
+ transitionProp.clear(&transitionProp);
+ if (transitionInStyle)
+ transitionProp.append(&transitionProp, transitionInStyle);
+ }
+
+ switch (m_alternateState) {
+ case AlternateState::Alternate1:
+ m_alternateState = AlternateState::Alternate2;
+ stateGroup()->setState(kAlternate2);
+ break;
+ case AlternateState::Alternate2:
+ m_alternateState = AlternateState::Alternate1;
+ stateGroup()->setState(kAlternate1);
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+}
+
+void QQStyleKitReader::resetAll()
+{
+ /* Tell all controls (or all QQuickStyleKitReaders inside all controls
+ * and elsewhere in the app, to be precise) to re-read all style properties.
+ * Optimization: do a pass of all the controls in the Normal state
+ * first, since we assume of those there will be many. Processing them
+ * first (that is, controls of the same state) means that we can limit
+ * the number of times we need to fully sync Style.target when
+ * doing property reads from QQStyleKitPropertyResolver. */
+ for (QQStyleKitReader *reader : s_allReaders) {
+ reader->m_effectiveVariationsDirty = true;
+ reader->m_hierarchyHasVariations = true;
+
+ /* Don't set m_parentChainDirty, since a theme or style change shouldn't affect
+ * the parent chain of any of the controls.
+ * TODO: add a public "StyleKit.forceLayout()" function that can be
+ * called whenever the app reparents controls in a way that can affect
+ * variations. This function should set m_parentChainDirty before calling this
+ * function. Parent changes could probably also be detected automatically, but
+ * its likely much more performant to put this burden on the app developer. */
+ // m_parentChainDirty = true;
+ }
+
+ for (QQStyleKitReader *reader : s_allReaders) {
+ if (reader->controlState() != QQSK::StateFlag::Normal)
+ continue;
+ reader->clearLocalStorage();
+ reader->emitChangedForAllStyleProperties();
+ }
+
+ for (QQStyleKitReader *reader : s_allReaders) {
+ if (reader->controlState() == QQSK::StateFlag::Normal)
+ continue;
+ reader->clearLocalStorage();
+ reader->emitChangedForAllStyleProperties();
+ }
+}
+
+void QQStyleKitReader::populateLocalStorage()
+{
+ if (!m_storage.isEmpty())
+ return;
+ const auto *stylePtr = style();
+ if (!stylePtr || !stylePtr->loaded())
+ return;
+
+ /* The local storage is empty, which is typically the case after an
+ * operation that should perform without a transition, such as a theme
+ * change or a change to a property value in the Style itself.
+ * Doing a transition in that case is unwanted and slow (since the
+ * operation typically affect all controls), so we short-cut the process by
+ * emitting changed signals directly for all the properties instead.
+ * Since that will render the local storage out-of-sync, we clear it at the
+ * same time to signal that it's 'dirty'. Which is why we need to sync it back
+ * up again now.
+ * Syncing the local storage before changing state (even if the values that
+ * end up in the storage should be exactly the same as those already
+ * showing), has the upshot that we can compare after the state change which
+ * properties has changed, which will limit the amount of changed signals we
+ * then need to emit. */
+ m_dontEmitChangedSignals = true;
+ updateControl();
+ m_dontEmitChangedSignals = false;
+}
+
+void QQStyleKitReader::clearLocalStorage()
+{
+ /* Clear all the local property overrides that has been set on this reader. Such
+ * overrides are typically interpolated values set by a transition during a state
+ * change. By clearing them, the controls will end up reading the property values
+ * directly from the Style instead. */
+ m_storage.clear();
+}
+
+QQSK::State QQStyleKitReader::controlState() const
+{
+ QQSK::State effectiveState = m_state;
+
+ if (!enabled()) {
+ // Some states are not valid if the control is disabled
+ effectiveState &= ~(QQSK::StateFlag::Pressed |
+ QQSK::StateFlag::Hovered |
+ QQSK::StateFlag::Highlighted |
+ QQSK::StateFlag::Focused |
+ QQSK::StateFlag::Hovered);
+ }
+
+ if (effectiveState == QQSK::StateFlag::NoState)
+ effectiveState.setFlag(QQSK::StateFlag::Normal);
+
+ return effectiveState;
+}
+
+QVariant QQStyleKitReader::readStyleProperty(PropertyStorageId key) const
+{
+ return m_storage.value(key);
+}
+
+void QQStyleKitReader::writeStyleProperty(PropertyStorageId key, const QVariant &value)
+{
+ m_storage.insert(key, value);
+}
+
+bool QQStyleKitReader::dontEmitChangedSignals() const
+{
+ return m_dontEmitChangedSignals;
+}
+
+int QQStyleKitReader::type() const
+{
+ return m_type;
+}
+
+void QQStyleKitReader::setType(int type)
+{
+ if (m_type == type)
+ return;
+
+ m_type = type;
+ populateLocalStorage();
+ emit typeChanged();
+ updateControl();
+}
+
+#ifdef QT_DEBUG
+QQStyleKitReader::ControlType QQStyleKitReader::typeAsControlType() const
+{
+ /* Note: m_type is of type int to support extending the list
+ * of possible types from the Style itself. This function
+ * is here to for debugging purposes */
+ return ControlType(m_type);
+}
+#endif
+
+bool QQStyleKitReader::hovered() const
+{
+ return m_state.testFlag(QQSK::StateFlag::Hovered);
+}
+
+void QQStyleKitReader::setHovered(bool hovered)
+{
+ if (hovered == QQStyleKitReader::hovered())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Hovered, hovered);
+ emit hoveredChanged();
+ updateControl();
+}
+
+bool QQStyleKitReader::enabled() const
+{
+ return !m_state.testFlag(QQSK::StateFlag::Disabled);
+}
+
+void QQStyleKitReader::setEnabled(bool enabled)
+{
+ if (enabled == QQStyleKitReader::enabled())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Disabled, !enabled);
+ emit enabledChanged();
+ updateControl();
+}
+
+bool QQStyleKitReader::focused() const
+{
+ return m_state.testFlag(QQSK::StateFlag::Focused);
+}
+
+void QQStyleKitReader::setFocused(bool focused)
+{
+ if (focused == QQStyleKitReader::focused())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Focused, focused);
+ emit focusedChanged();
+ updateControl();
+}
+
+bool QQStyleKitReader::checked() const
+{
+ return m_state.testFlag(QQSK::StateFlag::Checked);
+}
+
+void QQStyleKitReader::setChecked(bool checked)
+{
+ if (checked == QQStyleKitReader::checked())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Checked, checked);
+ emit checkedChanged();
+ updateControl();
+}
+
+bool QQStyleKitReader::pressed() const
+{
+ return m_state.testFlag(QQSK::StateFlag::Pressed);
+}
+
+void QQStyleKitReader::setPressed(bool pressed)
+{
+ if (pressed == QQStyleKitReader::pressed())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Pressed, pressed);
+ emit pressedChanged();
+ updateControl();
+}
+
+bool QQStyleKitReader::vertical() const
+{
+ return m_state.testFlag(QQSK::StateFlag::Vertical);
+}
+
+void QQStyleKitReader::setVertical(bool vertical)
+{
+ if (vertical == QQStyleKitReader::vertical())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Vertical, vertical);
+ emit verticalChanged();
+ updateControl();
+}
+
+bool QQStyleKitReader::highlighted() const
+{
+ return m_state.testFlag(QQSK::StateFlag::Highlighted);
+}
+
+void QQStyleKitReader::setHighlighted(bool highlighted)
+{
+ if (highlighted == QQStyleKitReader::highlighted())
+ return;
+
+ populateLocalStorage();
+ m_state.setFlag(QQSK::StateFlag::Highlighted, highlighted);
+ emit highlightedChanged();
+ updateControl();
+}
+
+QQuickPalette *QQStyleKitReader::palette() const
+{
+ return &const_cast<QQStyleKitReader *>(this)->m_palette;
+}
+
+void QQStyleKitReader::setPalette(QQuickPalette *palette)
+{
+ if (palette && m_palette.toQPalette() == palette->toQPalette())
+ return;
+
+ m_palette.reset();
+ if (palette)
+ m_palette.inheritPalette(palette->toQPalette());
+ emit paletteChanged();
+
+ const auto *stylePtr = style();
+ if (!stylePtr || !stylePtr->loaded())
+ return;
+
+ clearLocalStorage();
+ emitChangedForAllStyleProperties();
+}
+
+QQStyleKitControlProperties *QQStyleKitReader::global() const
+{
+ return &const_cast<QQStyleKitReader *>(this)->m_global;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitreader_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitreader_p.h b/src/labs/stylekit/qqstylekitreader_p.h
new file mode 100644
index 0000000000..f143a3cf53
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitreader_p.h
@@ -0,0 +1,182 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITREADER_P_H
+#define QQSTYLEKITREADER_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 <QtQml/QtQml>
+#include <QtQuick/private/qquickpalette_p.h>
+
+#include "qqstylekitcontrolproperties_p.h"
+#include "qqstylekitstorage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQuickPalette;
+class QQStyleKitVariation;
+class QQStyleKitPropertyResolver;
+
+class QQStyleKitReader : public QQStyleKitControlProperties
+{
+ Q_OBJECT
+ Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged FINAL)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL)
+ Q_PROPERTY(bool focused READ focused WRITE setFocused NOTIFY focusedChanged FINAL)
+ Q_PROPERTY(bool checked READ checked WRITE setChecked NOTIFY checkedChanged FINAL)
+ Q_PROPERTY(bool hovered READ hovered WRITE setHovered NOTIFY hoveredChanged FINAL)
+ Q_PROPERTY(bool pressed READ pressed WRITE setPressed NOTIFY pressedChanged FINAL)
+ Q_PROPERTY(bool vertical READ vertical WRITE setVertical NOTIFY verticalChanged FINAL)
+ Q_PROPERTY(bool highlighted READ highlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL)
+ Q_PROPERTY(QQuickPalette *palette READ palette WRITE setPalette NOTIFY paletteChanged FINAL)
+ Q_PROPERTY(QQStyleKitControlProperties *global READ global CONSTANT FINAL)
+
+ QML_NAMED_ELEMENT(StyleKitReader)
+
+public:
+ enum ControlType {
+ Unknown = 100000,
+ Control,
+ AbstractButton,
+ Button,
+ CheckBox,
+ ComboBox,
+ FlatButton,
+ Slider,
+ SpinBox,
+ SwitchControl,
+ SearchField,
+ TextArea,
+ TextField,
+ TextInput,
+ RadioButton,
+ ItemDelegate,
+ Popup,
+ Menu,
+ Dialog,
+ Pane,
+ Page,
+ Frame
+ };
+ Q_ENUM(ControlType)
+
+ enum class AlternateState {
+ Alternate1,
+ Alternate2
+ };
+ Q_ENUM(AlternateState)
+
+ QQStyleKitReader(QObject *parent = nullptr);
+ ~QQStyleKitReader();
+
+ int type() const;
+ void setType(int type);
+#ifdef QT_DEBUG
+ ControlType typeAsControlType() const;
+#endif
+
+ bool hovered() const;
+ void setHovered(bool hovered);
+
+ bool enabled() const;
+ void setEnabled(bool enabled);
+
+ bool focused() const;
+ void setFocused(bool focused);
+
+ bool checked() const;
+ void setChecked(bool checked);
+
+ bool pressed() const;
+ void setPressed(bool pressed);
+
+ QQuickPalette *palette() const;
+ void setPalette(QQuickPalette *palette);
+
+ bool vertical() const;
+ void setVertical(bool vertical);
+
+ bool highlighted() const;
+ void setHighlighted(bool highlighted);
+
+ QQStyleKitControlProperties *global() const;
+
+ QVariant readStyleProperty(PropertyStorageId key) const;
+ void writeStyleProperty(PropertyStorageId key, const QVariant &value);
+ void clearLocalStorage();
+
+ QQSK::State controlState() const;
+
+ static void setTransitionEnabled(bool enabled);
+ static bool transitionEnabled();
+ static void resetAll();
+
+ static QList<QQStyleKitReader *> s_allReaders;
+
+signals:
+ void typeChanged();
+ void customTypeChanged();
+ void propertiesChanged();
+ void enabledChanged();
+ void focusedChanged();
+ void checkedChanged();
+ void hoveredChanged();
+ void pressedChanged();
+ void paletteChanged();
+ void verticalChanged();
+ void highlightedChanged();
+
+private:
+ void updateControl();
+ void populateLocalStorage();
+ bool dontEmitChangedSignals() const;
+
+ QQuickStateGroup *stateGroup();
+ QQmlComponent *createControlChangesComponent() const;
+ QQmlComponent *createDelegateChangesComponent(const QString &delegateName) const;
+ void instantiatePropertyChanges(QQmlComponent *comp);
+ void maybeTrackDelegates();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitReader)
+
+ int m_type = ControlType::Unknown;
+
+ bool m_dontEmitChangedSignals: 1;
+ bool m_effectiveVariationsDirty: 1;
+ bool m_parentChainDirty: 1;
+ bool m_hierarchyHasVariations: 1;
+
+ QQuickPalette m_palette;
+ mutable QQStyleKitPropertyStorage m_storage;
+ AlternateState m_alternateState = AlternateState::Alternate1;
+ QQSK::State m_state = QQSK::StateFlag::NoState;
+ QQuickStateGroup *m_stateGroup = nullptr;
+ QQSK::Delegates m_trackedDelegates = QQSK::Delegate::NoDelegate;
+
+ QPointer<QQStyleKitReader> m_parentReader;
+ QList<QQStyleKitVariation *> m_effectiveVariations;
+
+ QQStyleKitControlProperties m_global;
+
+ static QMap<QString, QQmlComponent *> s_propertyChangesComponents;
+
+ friend class QQStyleKitControlProperties;
+ friend class QQStyleKitPropertyResolver;
+ friend class QQStyleKitPropertyGroup;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITREADER_P_H
diff --git a/src/labs/stylekit/qqstylekitstorage.cpp b/src/labs/stylekit/qqstylekitstorage.cpp
new file mode 100644
index 0000000000..6a62910573
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitstorage.cpp
@@ -0,0 +1,21 @@
+// 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
+
+#include "qqstylekitstorage_p.h"
+
+QT_BEGIN_NAMESPACE
+
+PropertyPathId::PropertyPathId(
+ const QQSK::Property property, PropertyPathId_t id, uint numberOfPropertiesInGroup)
+ : m_property(property), m_id(id), m_numberOfPropertiesInGroup(numberOfPropertiesInGroup)
+{
+}
+
+PropertyStorageId PropertyPathId::storageId(QQSK::State state) const
+{
+ return m_id + (m_numberOfPropertiesInGroup * state);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitstorage_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitstorage_p.h b/src/labs/stylekit/qqstylekitstorage_p.h
new file mode 100644
index 0000000000..9b49f2446c
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitstorage_p.h
@@ -0,0 +1,49 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITSTORAGE_P_H
+#define QQSTYLEKITSTORAGE_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 <QtCore/QtCore>
+
+#include "qqstylekitglobal_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using PropertyPathId_t = uint;
+using PropertyStorageId = uint;
+using QQStyleKitPropertyStorage = QMap<PropertyStorageId, QVariant>;
+
+class PropertyPathId {
+ Q_GADGET
+
+public:
+ PropertyPathId(
+ const QQSK::Property property = QQSK::Property::NoProperty,
+ PropertyPathId_t id = 0, uint numberOfPropertiesInGroup = 0);
+
+ QQSK::Property property() const { return m_property; }
+ PropertyPathId_t pathId() const { return m_id; }
+ PropertyStorageId storageId(QQSK::State state) const;
+
+private:
+ QQSK::Property m_property;
+ PropertyPathId_t m_id;
+ uint m_numberOfPropertiesInGroup;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITSTORAGE_P_H
diff --git a/src/labs/stylekit/qqstylekitstyle.cpp b/src/labs/stylekit/qqstylekitstyle.cpp
new file mode 100644
index 0000000000..b0b3cedb6f
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitstyle.cpp
@@ -0,0 +1,304 @@
+// 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
+
+#include "qqstylekit_p.h"
+#include "qqstylekitstyle_p.h"
+#include "qqstylekittheme_p.h"
+#include "qqstylekitcustomtheme_p.h"
+#include "qqstylekitcontrolproperties_p.h"
+#include "qqstylekitpropertyresolver_p.h"
+
+#include <QtQuickTemplates2/private/qquickdeferredexecute_p_p.h>
+#include <QtQml/private/qqmllist_p.h>
+
+#include <QtGui/QGuiApplication>
+#include <QtGui/QStyleHints>
+
+QT_BEGIN_NAMESPACE
+
+static const QString kSystem = "System"_L1;
+static const QString kLight = "Light"_L1;
+static const QString kDark = "Dark"_L1;
+
+QQStyleKitStyle::QQStyleKitStyle(QObject *parent)
+ : QQStyleKitControls(parent)
+ , m_themeName(kSystem)
+{
+}
+
+QQStyleKitStyle::~QQStyleKitStyle()
+{
+ if (m_theme)
+ m_theme->deleteLater();
+}
+
+QQuickPalette *QQStyleKitStyle::palette()
+{
+ if (!m_palette) {
+ static QQuickPalette placeholder;
+ return &placeholder;
+ }
+ return m_palette.get();
+}
+
+QQmlComponent *QQStyleKitStyle::light() const
+{
+ return m_light;
+}
+
+QQStyleKitStyle *QQStyleKitStyle::fallbackStyle() const
+{
+ if (!m_fallbackStyle) {
+ auto *self = const_cast<QQStyleKitStyle *>(this);
+ self->executeFallbackStyle();
+ }
+ return m_fallbackStyle;
+}
+
+void QQStyleKitStyle::setFallbackStyle(QQStyleKitStyle *fallbackStyle)
+{
+ if (m_fallbackStyle == fallbackStyle)
+ return;
+
+ m_fallbackStyle = fallbackStyle;
+ emit fallbackStyleChanged();
+}
+
+void QQStyleKitStyle::setLight(QQmlComponent *lightTheme)
+{
+ if (m_light == lightTheme)
+ return;
+
+ m_light = lightTheme;
+
+ emit lightChanged();
+}
+
+QQmlComponent *QQStyleKitStyle::dark() const
+{
+ return m_dark;
+}
+
+void QQStyleKitStyle::setDark(QQmlComponent *darkTheme)
+{
+ if (m_dark == darkTheme)
+ return;
+
+ m_dark = darkTheme;
+
+ emit darkChanged();
+}
+
+QQStyleKitTheme *QQStyleKitStyle::theme() const
+{
+ return m_theme;
+}
+
+QList<QObject *> QQStyleKitStyle::customThemesAsList()
+{
+ QList<QObject *> list;
+ for (auto *customTheme : customThemes())
+ list.append(customTheme);
+ return list;
+}
+
+QList<QQStyleKitCustomTheme *> QQStyleKitStyle::customThemes() const
+{
+ QList<QQStyleKitCustomTheme *> list;
+ for (auto *obj : children()) {
+ if (auto *customTheme = qobject_cast<QQStyleKitCustomTheme *>(obj))
+ list.append(customTheme);
+ }
+ return list;
+}
+
+void QQStyleKitStyle::parseThemes()
+{
+ m_themeNames = QStringList({kSystem, kLight, kDark});
+
+ for (auto *customTheme : customThemes()) {
+ const QString name = customTheme->name();
+ if (name.isEmpty())
+ continue;
+ m_themeNames << name;
+ m_customThemeNames << name;
+ }
+
+ emit themeNamesChanged();
+ emit customThemeNamesChanged();
+}
+
+QString QQStyleKitStyle::themeName() const
+{
+ return m_themeName;
+}
+
+QStringList QQStyleKitStyle::themeNames() const
+{
+ return m_themeNames;
+}
+
+QStringList QQStyleKitStyle::customThemeNames() const
+{
+ return m_customThemeNames;
+}
+
+void QQStyleKitStyle::setThemeName(const QString &themeName)
+{
+ if (m_themeName == themeName)
+ return;
+
+ m_themeName = themeName;
+ recreateTheme();
+
+ emit themeNameChanged();
+}
+
+void QQStyleKitStyle::recreateTheme()
+{
+ QString effectiveThemeName;
+ QQmlComponent *effectiveThemeComponent = nullptr;
+
+ if (QString::compare(m_themeName, kSystem, Qt::CaseInsensitive) == 0) {
+ const auto scheme = QGuiApplication::styleHints()->colorScheme();
+ if (scheme == Qt::ColorScheme::Light) {
+ effectiveThemeName = kLight;
+ effectiveThemeComponent = m_light;
+ }
+ else if (scheme == Qt::ColorScheme::Dark) {
+ effectiveThemeName =kDark;
+ effectiveThemeComponent = m_dark;
+ }
+ } else if (QString::compare(m_themeName, kLight, Qt::CaseInsensitive) == 0) {
+ effectiveThemeName = kLight;
+ effectiveThemeComponent = m_light;
+ } else if (QString::compare(m_themeName,kDark, Qt::CaseInsensitive) == 0) {
+ effectiveThemeName =kDark;
+ effectiveThemeComponent = m_dark;
+ } else if (!m_themeName.isEmpty()){
+ for (auto *customTheme : customThemes()) {
+ if (QString::compare(m_themeName, customTheme->name(), Qt::CaseInsensitive) == 0) {
+ effectiveThemeName = customTheme->name();
+ effectiveThemeComponent = customTheme->theme();
+ break;
+ }
+ }
+ if (effectiveThemeName.isEmpty())
+ qmlWarning(this) << "No theme found with name:" << m_themeName;
+ else if (!effectiveThemeComponent)
+ qmlWarning(this) << "Custom theme '" << effectiveThemeName << "' has no theme component set";
+ }
+
+ if (m_effectiveThemeName == effectiveThemeName) {
+ // Switching theme name from e.g "System" to "Light" might not
+ // actually change the currently effective theme.
+ emit themeNameChanged();
+ return;
+ }
+
+ if (m_theme) {
+ m_theme->deleteLater();
+ m_theme = nullptr;
+ }
+
+ m_currentThemeComponent = effectiveThemeComponent;
+
+ if (effectiveThemeComponent) {
+ if (effectiveThemeComponent->status() != QQmlComponent::Ready) {
+ qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
+ } else {
+ /* The 'createThemeInsideStyle' JS function is a work-around since we haven't found
+ * a way to instantiate a Theme inside the context of a Style from c++. Doing so is
+ * needed in order to allow custom style properties to be added as children of a
+ * Style, and at the same time, be able to access them from within a Theme. For
+ * this to work, the Style also needs to set 'pragma ComponentBehavior: Bound'. */
+ QVariant themeAsVariant;
+ QMetaObject::invokeMethod(this, "createThemeInsideStyle", Qt::DirectConnection,
+ qReturnArg(themeAsVariant), QVariant::fromValue(effectiveThemeComponent));
+ m_theme = qvariant_cast<QQStyleKitTheme *>(themeAsVariant);
+
+ if (!m_theme || !effectiveThemeComponent->errorString().isEmpty()) {
+ qmlWarning(this) << "failed to create theme '" << effectiveThemeName << "': " << effectiveThemeComponent->errorString();
+ } else {
+ m_theme->setParent(this);
+ }
+ }
+ }
+
+ if (!m_theme) {
+ // We always require a theme, even if it's empty
+ m_theme = new QQStyleKitTheme(this);
+ m_theme->setObjectName("<empty theme>"_L1);
+ m_theme->m_completed = true;
+ } else {
+ m_theme->setParent(this);
+ }
+
+ if (this == current()) {
+ m_theme->updateQuickTheme();
+ QQStyleKitReader::resetAll();
+ }
+
+ emit themeChanged();
+}
+
+QQStyleKitStyle* QQStyleKitStyle::current()
+{
+ return QQStyleKit::qmlAttachedProperties()->style();
+}
+
+bool QQStyleKitStyle::loaded() const
+{
+ /* Before both the style and theme has completed loading
+ * we return false. This can be used to avoid unnecessary
+ * property reads when we anyway have to do a full update
+ * in the end. */
+ if (!m_completed)
+ return false;
+ if (!m_theme || !m_theme->m_completed)
+ return false;
+
+ return true;
+}
+
+void QQStyleKitStyle::executeFallbackStyle(bool complete)
+{
+ if (m_fallbackStyle.wasExecuted())
+ return;
+
+ const QString name = "fallbackStyle"_L1;
+ if (!m_fallbackStyle || complete)
+ quickBeginDeferred(this, name, m_fallbackStyle);
+ if (complete)
+ quickCompleteDeferred(this, name, m_fallbackStyle);
+}
+
+void QQStyleKitStyle::setPalette(QQuickPalette *palette)
+{
+ if (m_palette && palette && m_palette->toQPalette() == palette->toQPalette())
+ return;
+
+ m_palette = palette;
+
+ QScopedValueRollback rollback(m_isUpdatingPalette, true);
+ emit paletteChanged();
+}
+
+void QQStyleKitStyle::componentComplete()
+{
+ QQStyleKitControls::componentComplete();
+
+ /* It's important to set m_completed before creating the theme, otherwise
+ * styleAndThemeFinishedLoading() will still be false, which will e.g cause
+ * property reads to return early from QQStyleKitPropertyResolver */
+ m_completed = true;
+
+ executeFallbackStyle(true);
+ parseThemes();
+ recreateTheme();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitstyle_p.cpp"
+
diff --git a/src/labs/stylekit/qqstylekitstyle_p.h b/src/labs/stylekit/qqstylekitstyle_p.h
new file mode 100644
index 0000000000..b7deae4aff
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitstyle_p.h
@@ -0,0 +1,125 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITSTYLE_P_H
+#define QQSTYLEKITSTYLE_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 "qqstylekitreader_p.h"
+#include "qqstylekitcontrols_p.h"
+#include "qqstylekitcustomtheme_p.h"
+#include "qqstylekitdebug_p.h"
+
+#include <QtQml/QtQml>
+#include <QtQuickTemplates2/private/qquickdeferredpointer_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitTheme;
+class QQStyleKitPropertyResolver;
+
+class QQStyleKitStyle : public QQStyleKitControls
+{
+ Q_OBJECT
+ Q_PROPERTY(QQuickPalette *palette READ palette NOTIFY paletteChanged FINAL)
+ Q_PROPERTY(QQStyleKitStyle *fallbackStyle READ fallbackStyle WRITE setFallbackStyle NOTIFY fallbackStyleChanged FINAL)
+ Q_PROPERTY(QQmlComponent *light READ light WRITE setLight NOTIFY lightChanged FINAL)
+ Q_PROPERTY(QQmlComponent *dark READ dark WRITE setDark NOTIFY darkChanged FINAL)
+ Q_PROPERTY(QString themeName READ themeName WRITE setThemeName NOTIFY themeNameChanged FINAL)
+ Q_PROPERTY(QStringList themeNames READ themeNames NOTIFY themeNamesChanged FINAL)
+ Q_PROPERTY(QStringList customThemeNames READ customThemeNames NOTIFY customThemeNamesChanged FINAL)
+ Q_PROPERTY(QQStyleKitTheme *theme READ theme NOTIFY themeChanged FINAL)
+
+ Q_CLASSINFO("DeferredPropertyNames", "fallbackStyle")
+ QML_NAMED_ELEMENT(BaseStyle)
+
+public:
+ enum Contstants {
+ Stretch = -1,
+ };
+ Q_ENUM(Contstants)
+
+ QQStyleKitStyle(QObject *parent = nullptr);
+ ~QQStyleKitStyle();
+
+ QQuickPalette *palette();
+
+ QQStyleKitStyle *fallbackStyle() const;
+ void setFallbackStyle(QQStyleKitStyle *fallbackStyle);
+
+ QQmlComponent *light() const;
+ void setLight(QQmlComponent *lightTheme);
+
+ QQmlComponent *dark() const;
+ void setDark(QQmlComponent *darkTheme);
+
+ QList<QQStyleKitCustomTheme *> customThemes() const;
+ QStringList themeNames() const;
+ QStringList customThemeNames() const;
+
+ void setThemeName(const QString &themeName);
+ QString themeName() const;
+ QQStyleKitTheme *theme() const;
+
+ bool loaded() const;
+
+ static QQStyleKitStyle *current();
+
+ // For now, used by qqcontrolstowidgetstyle
+ Q_INVOKABLE QList<QObject *> customThemesAsList();
+
+signals:
+ void paletteChanged();
+ void fallbackStyleChanged();
+ void lightChanged();
+ void darkChanged();
+ void themeChanged();
+ void themeNameChanged();
+ void themeNamesChanged();
+ void customThemeNamesChanged();
+
+protected:
+ void componentComplete() override;
+
+private:
+ void parseThemes();
+ void recreateTheme();
+ void executeFallbackStyle(bool complete = false);
+ void setPalette(QQuickPalette *palette);
+
+private:
+ Q_DISABLE_COPY(QQStyleKitStyle)
+
+ bool m_completed = false;
+ bool m_isUpdatingPalette = false;
+
+ QQuickDeferredPointer<QQStyleKitStyle> m_fallbackStyle;
+ QPointer<QQmlComponent> m_light;
+ QPointer<QQmlComponent> m_dark;
+ QPointer<QQStyleKitTheme> m_theme;
+ QPointer<QQmlComponent> m_currentThemeComponent;
+ QPointer<QQuickPalette> m_palette;
+ QString m_themeName;
+ QString m_effectiveThemeName;
+ QStringList m_themeNames;
+ QStringList m_customThemeNames;
+
+ friend class QQStyleKitAttached;
+ friend class QQStyleKitPropertyGroup;
+ friend class QQStyleKitPropertyResolver;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITSTYLE_P_H
diff --git a/src/labs/stylekit/qqstylekittheme.cpp b/src/labs/stylekit/qqstylekittheme.cpp
new file mode 100644
index 0000000000..08cec449c9
--- /dev/null
+++ b/src/labs/stylekit/qqstylekittheme.cpp
@@ -0,0 +1,123 @@
+// 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
+
+#include <QtQuickTemplates2/private/qquicktheme_p.h>
+#include <QtQuick/private/qquickpalette_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformtheme.h>
+
+#include "qqstylekittheme_p.h"
+#include "qqstylekitstyle_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitTheme::QQStyleKitTheme(QObject *parent)
+ : QQStyleKitControls(parent)
+{
+}
+
+QQStyleKitStyle *QQStyleKitTheme::style() const
+{
+ QObject *parentObj = parent();
+ if (!parentObj)
+ return nullptr;
+ Q_ASSERT(qobject_cast<QQStyleKitStyle *>(parentObj));
+ return static_cast<QQStyleKitStyle *>(parentObj);
+}
+
+QQStyleKitPalette *QQStyleKitTheme::palettes()
+{
+ return &m_palettes;
+}
+
+// Copied from QQuickTheme
+static QPlatformTheme::Palette toPlatformThemePalette(QQuickTheme::Scope scope)
+{
+ switch (scope) {
+ case QQuickTheme::Button: return QPlatformTheme::ButtonPalette;
+ case QQuickTheme::CheckBox: return QPlatformTheme::CheckBoxPalette;
+ case QQuickTheme::ComboBox: return QPlatformTheme::ComboBoxPalette;
+ case QQuickTheme::GroupBox: return QPlatformTheme::GroupBoxPalette;
+ case QQuickTheme::ItemView: return QPlatformTheme::ItemViewPalette;
+ case QQuickTheme::Label: return QPlatformTheme::LabelPalette;
+ case QQuickTheme::ListView: return QPlatformTheme::ItemViewPalette;
+ case QQuickTheme::Menu: return QPlatformTheme::MenuPalette;
+ case QQuickTheme::MenuBar: return QPlatformTheme::MenuBarPalette;
+ case QQuickTheme::RadioButton: return QPlatformTheme::RadioButtonPalette;
+ case QQuickTheme::SpinBox: return QPlatformTheme::TextLineEditPalette;
+ case QQuickTheme::Switch: return QPlatformTheme::CheckBoxPalette;
+ case QQuickTheme::TabBar: return QPlatformTheme::TabBarPalette;
+ case QQuickTheme::TextArea: return QPlatformTheme::TextEditPalette;
+ case QQuickTheme::TextField: return QPlatformTheme::TextLineEditPalette;
+ case QQuickTheme::ToolBar: return QPlatformTheme::ToolButtonPalette;
+ case QQuickTheme::ToolTip: return QPlatformTheme::ToolTipPalette;
+ case QQuickTheme::Tumbler: return QPlatformTheme::ItemViewPalette;
+ default: return QPlatformTheme::SystemPalette;
+ }
+}
+
+void QQStyleKitTheme::updateThemePalette()
+{
+ auto *theme = QQuickTheme::instance();
+ if (!theme)
+ return;
+
+ // QQuickTheme currently offers only one default platform palette for all controls.
+ // This implementation will instead inherit the corresponding platform palette for each
+ // control type. Hence, for now, we don't use QQuickTheme::usePlatformPalette, but roll
+ // our own version instead.
+ theme->setUsePlatformPalette(false);
+
+ const auto *platformTheme = QGuiApplicationPrivate::platformTheme();
+
+#define SET_PALETTE(CONTROL, SCOPE) { \
+ const QQuickPalette *controlPalette = m_palettes.CONTROL(); \
+ const QPalette *platformPalette = platformTheme->palette(toPlatformThemePalette(SCOPE)); \
+ if (controlPalette && platformPalette) { \
+ QPalette resolved = controlPalette->toQPalette().resolve(*platformPalette); \
+ theme->setPalette(SCOPE, resolved); \
+ } else if (platformPalette) { \
+ theme->setPalette(SCOPE, *platformPalette); \
+ } else if (controlPalette) { \
+ theme->setPalette(SCOPE, controlPalette->toQPalette()); \
+ } \
+}
+
+ SET_PALETTE(system, QQuickTheme::System);
+ SET_PALETTE(button, QQuickTheme::Button);
+ SET_PALETTE(checkBox, QQuickTheme::CheckBox);
+ SET_PALETTE(comboBox, QQuickTheme::ComboBox);
+ SET_PALETTE(groupBox, QQuickTheme::GroupBox);
+ SET_PALETTE(itemView, QQuickTheme::ItemView);
+ SET_PALETTE(label, QQuickTheme::Label);
+ SET_PALETTE(listView, QQuickTheme::ListView);
+ SET_PALETTE(menu, QQuickTheme::Menu);
+ SET_PALETTE(menuBar, QQuickTheme::MenuBar);
+ SET_PALETTE(radioButton, QQuickTheme::RadioButton);
+ SET_PALETTE(spinBox, QQuickTheme::SpinBox);
+ SET_PALETTE(switchControl, QQuickTheme::Switch);
+ SET_PALETTE(tabBar, QQuickTheme::TabBar);
+ SET_PALETTE(textArea, QQuickTheme::TextArea);
+ SET_PALETTE(textField, QQuickTheme::TextField);
+ SET_PALETTE(toolBar, QQuickTheme::ToolBar);
+ SET_PALETTE(toolTip, QQuickTheme::ToolTip);
+ SET_PALETTE(tumbler, QQuickTheme::Tumbler);
+
+ QEvent event(QEvent::ApplicationPaletteChange);
+ QGuiApplication::sendEvent(qGuiApp, &event);
+}
+
+void QQStyleKitTheme::updateQuickTheme()
+{
+ updateThemePalette();
+}
+
+void QQStyleKitTheme::componentComplete()
+{
+ QQStyleKitControls::componentComplete();
+ m_completed = true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekittheme_p.cpp"
diff --git a/src/labs/stylekit/qqstylekittheme_p.h b/src/labs/stylekit/qqstylekittheme_p.h
new file mode 100644
index 0000000000..8096e9935b
--- /dev/null
+++ b/src/labs/stylekit/qqstylekittheme_p.h
@@ -0,0 +1,65 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITTHEME_P_H
+#define QQSTYLEKITTHEME_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 <QtQml/QtQml>
+
+#include "qqstylekitcontrols_p.h"
+#include "qqstylekitpalette_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitControls;
+class QQStyleKitPropertyResolver;
+
+class QQStyleKitTheme : public QQStyleKitControls
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QQStyleKitPalette *palettes READ palettes NOTIFY palettesChanged FINAL)
+
+ QML_NAMED_ELEMENT(Theme)
+
+public:
+ QQStyleKitTheme(QObject *parent = nullptr);
+
+ QQStyleKitStyle *style() const;
+ QQStyleKitPalette *palettes();
+
+signals:
+ void targetChanged();
+ void palettesChanged();
+
+protected:
+ void componentComplete() override;
+
+private:
+ Q_DISABLE_COPY(QQStyleKitTheme)
+
+ bool m_completed = false;
+ QQStyleKitPalette m_palettes;
+
+ void updateThemePalette();
+ void updateQuickTheme();
+
+ friend class QQStyleKitAttached;
+ friend class QQStyleKitStyle;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITTHEME_P_H
diff --git a/src/labs/stylekit/qqstylekitthemeproperties.cpp b/src/labs/stylekit/qqstylekitthemeproperties.cpp
new file mode 100644
index 0000000000..7d342c563b
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitthemeproperties.cpp
@@ -0,0 +1,22 @@
+// 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
+
+#include "qqstylekitthemeproperties_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitThemeProperties::QQStyleKitThemeProperties(QObject *parent)
+ : QQStyleKitControls(parent)
+{
+}
+
+QQStyleKitFont *QQStyleKitThemeProperties::fonts()
+{
+ return &m_fonts;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitthemeproperties_p.cpp"
+
+
diff --git a/src/labs/stylekit/qqstylekitthemeproperties_p.h b/src/labs/stylekit/qqstylekitthemeproperties_p.h
new file mode 100644
index 0000000000..5d8985441b
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitthemeproperties_p.h
@@ -0,0 +1,49 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QQSTYLEKITTHEMEPROPERTIES_P_H
+#define QQSTYLEKITTHEMEPROPERTIES_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 <QtQml/QtQml>
+#include "qqstylekitcontrols_p.h"
+#include "qqstylekitfont_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitThemeProperties : public QQStyleKitControls
+{
+ Q_OBJECT
+ Q_PROPERTY(QQStyleKitFont *fonts READ fonts NOTIFY fontsChanged FINAL)
+ //TODO: Move palettes property here as well
+ QML_UNCREATABLE("This component is abstract, and cannot be instantiated")
+ QML_NAMED_ELEMENT(ThemeProperties)
+
+public:
+ QQStyleKitThemeProperties(QObject *parent = nullptr);
+
+ QQStyleKitFont *fonts();
+
+signals:
+ void fontsChanged();
+
+private:
+ Q_DISABLE_COPY(QQStyleKitThemeProperties)
+
+ QQStyleKitFont m_fonts;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITTHEMEPROPERTIES_P_H
diff --git a/src/labs/stylekit/qqstylekitvariation.cpp b/src/labs/stylekit/qqstylekitvariation.cpp
new file mode 100644
index 0000000000..4c224c2fb7
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitvariation.cpp
@@ -0,0 +1,15 @@
+// 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
+
+#include "qqstylekitvariation_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQStyleKitVariation::QQStyleKitVariation(QObject *parent)
+ : QQStyleKitControls(parent)
+{
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqstylekitvariation_p.cpp"
diff --git a/src/labs/stylekit/qqstylekitvariation_p.h b/src/labs/stylekit/qqstylekitvariation_p.h
new file mode 100644
index 0000000000..07fe440b84
--- /dev/null
+++ b/src/labs/stylekit/qqstylekitvariation_p.h
@@ -0,0 +1,38 @@
+// 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 QQSTYLEKITVARIATION_P_H
+#define QQSTYLEKITVARIATION_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 <QtQml/QtQml>
+
+#include "qqstylekitcontrols_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QQStyleKitVariation : public QQStyleKitControls
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(Variation)
+
+public:
+ QQStyleKitVariation(QObject *parent = nullptr);
+
+private:
+ Q_DISABLE_COPY(QQStyleKitVariation)
+};
+
+QT_END_NAMESPACE
+
+#endif // QQSTYLEKITVARIATION_P_H
diff --git a/src/quickcontrols/configure.cmake b/src/quickcontrols/configure.cmake
index 62da4f2fb7..75e1e440c1 100644
--- a/src/quickcontrols/configure.cmake
+++ b/src/quickcontrols/configure.cmake
@@ -50,6 +50,12 @@ qt_feature("quickcontrols2-fluentwinui3" PRIVATE
PURPOSE "Provides a style based on the Fluent design and Windows UI 3 style."
CONDITION QT_FEATURE_quickcontrols2_fusion
)
+qt_feature("quickcontrols2-stylekit" PRIVATE
+ SECTION "Quick Controls 2"
+ LABEL "StyleKit"
+ PURPOSE "Provides a styling framework."
+ CONDITION QT_FEATURE_quickcontrols2_basic
+)
qt_feature("quickcontrols2-macos" PRIVATE
SECTION "Quick Controls 2"
LABEL "macOS"
@@ -74,4 +80,5 @@ qt_configure_add_summary_entry(
ARGS "quickcontrols2-basic quickcontrols2-fusion quickcontrols2-fluentwinui3 quickcontrols2-imagine quickcontrols2-ios quickcontrols2-material quickcontrols2-universal quickcontrols2-macos quickcontrols2-windows"
MESSAGE "Styles"
)
+qt_configure_add_summary_entry(ARGS "quickcontrols2-stylekit")
qt_configure_end_summary_section() # end of "Qt Quick Controls 2" section