Revision control
++ // 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"
++ #include "qqstylekitstyleandthemebase_p.h"
++ #include "qqstylekitstyle_p.h"
++ #include "qqstylekittheme_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype StyleVariation
++ \inqmlmodule Qt.labs.StyleKit
++ \inherits StylableControls
++ \brief Defines alternative styling for specific controls.
++
++
++ A StyleVariation lets you define alternative styling that can be applied
++ to specific controls in the application (\e{instance variations}),
++ or to all descendants of a particular parent control type
++ (\e{type variations}). For example, you can give controls in a sidebar a
++ compact appearance, or make buttons inside all \l {ToolBar}{toolbars} look
++ different from buttons elsewhere.
++
++ \section2 Instance Variations
++
++ Instance variations are activated by setting the \l {StyleVariation::}{variations}
++ attached property on any item in the application — all StyleKit controls that are
++ children or descendants of that item will receive the alternative styling. Variations
++ can also be set at multiple levels of the hierarchy simultaneously; when they conflict,
++ the variation closest to the control takes precedence.
++
++ Instance variations are defined in the \l Style or \l Theme that they affect:
++
++ \snippet InstanceVariationSnippets.qml instance variations in style
++
++ And they are applied from the application using the
++ \l {StyleVariation::}{variations} attached property:
++
++ \snippet InstanceVariationSnippets.qml apply instance variation
++
++ Variation names referred to from the application that are not defined in the active
++ style (or theme) are silently ignored — they act as hints that the style author can
++ choose to implement or leave unhandled.
++
++ \section2 Type Variations
++
++ Type variations are assigned to the \l {ControlStyle::}{variations} property of a
++ specific \l ControlStyle, without requiring a \l name. Unlike instance variations —
++ which require the application to explicitly opt in — type variations are applied
++ automatically to all controls of a given type whenever they are descendants of the
++ specified parent control type.
++
++ The following snippet shows how to make all \l {Button}{Buttons} inside a \l Frame look
++ distinct from buttons elsewhere:
++
++ \snippet TypeVariationSnippets.qml frame with variation
++
++ Because \l {StylableControls::}{groupBox} falls back to
++ \l {StylableControls::}{frame} in the style hierarchy, type variations
++ set on \c frame are automatically inherited by \c groupBox as well. To opt out,
++ reset the variations for the subtype.
++
++ \section2 Propagation order
++
++ A StyleVariation is local to the \l Style or \l Theme where it is defined. A Theme
++ cannot shadow an entire StyleVariation defined in the Style — only individual
++ properties within it can be overridden, just like any other style property.
++
++ For example, if both the \l Style and the active \l Theme define \c {frame.variations},
++ both type variation arrays take effect for a \l Frame. The resolution order for
++ individual properties is: the Theme's variation properties take precedence over the
++ Theme's direct properties, which take precedence over the Style's variation properties,
++ which take precedence over the Style's direct properties.
++
++ The same applies to instance variations. A StyleVariation defined in a \l Theme takes
++ precedence over the Theme's direct properties. If a StyleVariation with the same
++ \l name is also defined in the \l Style, both take effect, following the same
++ resolution order as described above.
++
++ In the following snippet, a button with \c {StyleVariation.variations: ["alert"]}
++ will be red in the light theme and cyan in the dark theme, with a 4-pixel border in
++ both. Because the dark theme overrides \c {button.background.radius} to be \c 6,
++ that property takes precedence over the \c {background.radius} set in the Style's
++ variation. As a result, the button's radius ends up being \c 6 in the dark theme, but \c 0
++ in the light theme:
++
++ \snippet VariationPropagation.qml propagation
++
++ \labs
++
++ \sa Style, Theme, {ControlStyle::variations}{ControlStyle.variations},
++ {StyleVariation::variations}{StyleVariation.variations, {StyleKit Property Resolution}}
++ */
++
++ /*!
++ \qmlproperty string StyleVariation::name
++
++ The name of this variation.
++
++ The name identifies this variation when used as an
++ \l {Instance Variations}{instance variation}.
++
++ \sa {StyleVariation::variations}{variations}, {Instance Variations}
++ */
++
++ /*!
++ \qmlattachedproperty list<string> StyleVariation::variations
++
++ This property holds the list of \l{StyleVariation}{instance variations}
++ to activate for a \l Control, and its descendant controls.
++
++ If multiple variations in the list sets the same property, the first
++ one in the list takes precedence.
++
++ If variations are set on both a parent item and a descendant control, both
++ sets apply, with the control's own variations taking precedence
++ over those inherited from the parent.
++
++ \snippet InstanceVariationSnippets.qml apply instance variation
++
++ \sa name
++ */
++
++ /*!
++ \qmlattachedproperty int StyleVariation::controlType
++
++ This property identifies the \l {StyleReader::controlType}{control type} of
++ the item it is attached to.
++
++ StyleKit uses it to resolve \l {Type Variations}{type variations} for descendant
++ controls — if a parent item's \c controlType matches a control type that has
++ \l {ControlStyle::}{variations} defined in the \l Style, those variations apply
++ to all descendant controls.
++
++ Built-in controls set this property automatically. Custom controls
++ must set it explicitly to participate in type variation resolution.
++
++ \sa {ControlStyle::variations}{ControlStyle.variations},
++ {StyleReader::controlType}{StyleReader.controlType}
++ */
++
++ QQStyleKitVariation::QQStyleKitVariation(QObject *parent)
++ : QQStyleKitControls(parent)
++ {
++ }
++
++ void QQStyleKitVariation::componentComplete()
++ {
++ QQStyleKitControls::componentComplete();
++
++ /* Whenever there is a Style or Theme change, the list of StyleVariations that affects a
++ * StyleReader needs to be rebuilt. And in order to know which StyleVariations that should
++ * be taken into consideration, we need to know which Style each StyleVariation belongs to. */
++ bool styleOrThemeFound = false;
++ for (QObject *current = parent(); current; current = current->parent()) {
++ if (auto *styleOrTheme = qobject_cast<QQStyleKitStyleAndThemeBase *>(current)) {
++ styleOrTheme->m_styleVariations.append(this);
++ styleOrThemeFound = true;
++ break;
++ }
++ }
++ if (!styleOrThemeFound)
++ qmlWarning(this) << "A StyleVariation needs to be a descendant of the Style it belongs to!";
++ }
++
++ QQStyleKitVariationAttached *QQStyleKitVariation::qmlAttachedProperties(QObject *object)
++ {
++ return new QQStyleKitVariationAttached(object);
++ }
++
++ QString QQStyleKitVariation::name() const
++ {
++ return m_name;
++ }
++
++ void QQStyleKitVariation::setName(const QString &name)
++ {
++ if (m_name == name)
++ return;
++
++ m_name = name;
++ emit nameChanged();
++ }
++
++ QQStyleKitVariationAttached::QQStyleKitVariationAttached(QObject *parent)
++ : QObject(parent)
++ {
++ }
++
++ QStringList QQStyleKitVariationAttached::variations() const
++ {
++ return m_variations;
++ }
++
++ void QQStyleKitVariationAttached::setVariations(const QStringList &variations)
++ {
++ if (m_variations == variations)
++ return;
++
++ m_variations = variations;
++ emit variationsChanged();
++ }
++
++ QQStyleKitExtendableControlType QQStyleKitVariationAttached::controlType() const
++ {
++ return m_controlType;
++ }
++
++ void QQStyleKitVariationAttached::setControlType(QQStyleKitExtendableControlType type)
++ {
++ if (m_controlType == type)
++ return;
++
++ m_controlType = type;
++ emit controlTypeChanged();
++ }
++
++ void QQStyleKitVariation::resetVariationsForStyle(QQStyleKitStyle *style)
++ {
++ /* The usage context stored in a StyleVariation tells which Style or Theme uses it.
++ * After a theme change, we therefore need to rebuild this context list, since the
++ * old theme needs to be removed, and the new theme might need to be added.
++ * Since we also need to update which variations will now be effective for the existing
++ * StyleReaders, we simply clear the context here, and leave it to QQStyleKitPropertyResolver
++ * to rebuild both the effective variations and the context in one pass later.
++ * (QQStyleKitReader::resetReadersForStyle() will set the m_effectiveVariationsDirty
++ * flag, which will trigger the rebuild).
++ *
++ * As an optimization, we also update m_hasVariations to reflect whether any variations
++ * exist at all. If not, variations don't need to be taken into consideration during
++ * style property look-up. */
++ style->m_hasVariations = false;
++
++ for (QQStyleKitStyle *current = style; current; current = current->fallbackStyle()) {
++ if (!current->m_styleVariations.isEmpty()) {
++ style->m_hasVariations = true;
++ for (QQStyleKitVariation *variation : current->m_styleVariations)
++ variation->m_usageContext.clear();
++ }
++
++ if (QQStyleKitTheme *theme = current->theme()) {
++ if (!theme->m_styleVariations.isEmpty()) {
++ style->m_hasVariations = true;
++ for (QQStyleKitVariation *variation : theme->m_styleVariations)
++ variation->m_usageContext.clear();
++ }
++ }
++ }
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqstylekitvariation_p.cpp"