diff options
| author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2025-11-19 15:49:47 +0100 |
|---|---|---|
| committer | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2025-11-27 12:22:49 +0100 |
| commit | c056aaa055187af7290d363d5ef02a4948cd8813 (patch) | |
| tree | d5df750ffefe1d9dbb366177587a9b151d113c78 /src/labs/stylekit/qqstylekitdebug.cpp | |
| parent | 8890b4a2d56ebdd51bf550d307f48d60ccf9a632 (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/labs/stylekit/qqstylekitdebug.cpp')
| -rw-r--r-- | src/labs/stylekit/qqstylekitdebug.cpp | 361 |
1 files changed, 361 insertions, 0 deletions
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" + |
