Revision control

++ // Copyright (C) 2026 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 "qqstylekitdelegate_p.h"
++ #include "qqstylekitcontrolproperties_p.h"
++
++ #include <QtQuick/private/qquickrectangle_p.h>
++ #include <QtQuick/private/qquickimplicitsizeitem_p_p.h>
++ #include <QtQuickControls2Impl/private/qquickcolorimage_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype StyledItem
++ \inqmlmodule Qt.labs.StyleKit
++ \inherits Item
++ \brief Renders a DelegateStyle.
++
++ StyledItem renders a \l DelegateStyle. It reads properties such as
++ \l {DelegateStyle::color}{color},
++ \l {DelegateStyle::border}{border},
++ \l {DelegateStyle::gradient}{gradient}, and
++ \l {DelegateStyle::image}{image}, and creates the corresponding
++ visual elements internally.
++
++ If the \l {DelegateStyle::delegate}{delegate} set on a DelegateStyle
++ is \c null (the default), StyledItem will automatically be used for rendering.
++
++ A custom \l {DelegateStyle::delegate}{delegate} can be set to
++ any Item. But combined with a StyledItem, you can retain
++ the default rendering while adding overlay, underlay, or shader effects.
++ The following snippet shows how to draw an extra Item on top of the
++ default delegate:
++
++ \snippet StyledItemOverlay.qml overlay
++
++ \labs
++
++ For more examples of overlays, underlays and shader effects, see the
++ \l{StyleKit Quick Controls Example}.
++
++ \sa DelegateStyle, {DelegateStyle::delegate}{delegate}, {DelegateStyle::data}{data}
++ */
++
++ /*!
++ \qmlproperty DelegateStyle StyledItem::delegateStyle
++
++ The \l DelegateStyle that this item renders.
++
++ This property is \l {Required Properties}{required}. When
++ StyledItem is the root item of a
++ \l {DelegateStyle::delegate}{delegate}, it is set automatically.
++ But when used as a child inside a custom delegate, it must be set
++ explicitly.
++
++ The following snippet uses a custom delegate that draws a star
++ underneath the default slider handle. Since the root item is not
++ a StyledItem, it declares a \l {Required Properties}{required}
++ property \c {delegateStyle} (which is assigned automatically), and
++ forwards it to the child StyledItem:
++
++ \snippet StyledItemUnderlay.qml underlay
++
++ \sa DelegateStyle, {DelegateStyle::delegate}{delegate}, {DelegateStyle::data}{data}
++ */
++
++ QQStyleKitDelegate::QQStyleKitDelegate(QQuickItem *parent)
++ : QQuickImplicitSizeItem(*new QQuickImplicitSizeItemPrivate, parent)
++ {
++ }
++
++ QQStyleKitDelegateProperties *QQStyleKitDelegate::delegateStyle() const
++ {
++ return m_delegateProperties;
++ }
++
++ void QQStyleKitDelegate::setDelegateStyle(QQStyleKitDelegateProperties *delegateStyle)
++ {
++ if (m_delegateProperties == delegateStyle)
++ return;
++
++ if (m_delegateProperties)
++ disconnect(m_delegateProperties, nullptr, this, nullptr);
++
++ m_delegateProperties = delegateStyle;
++
++ if (!qmlEngine(this)) {
++ qmlWarning(this) << "Unable to draw delegate: no QQmlEngine found";
++ return;
++ }
++
++ maybeCreateColor();
++ maybeCreateGradient();
++ maybeCreateImage();
++ updateImplicitSize();
++
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::widthChanged, this, &QQStyleKitDelegate::updateImplicitSize);
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::heightChanged, this, &QQStyleKitDelegate::updateImplicitSize);
++
++ emit delegateStyleChanged();
++ }
++
++ void QQStyleKitDelegate::updateImplicitSize()
++ {
++ if (!m_delegateProperties)
++ return;
++
++ /* The implicit size of this item is determined by the following priority:
++ * 1. The width/height set on DelegateStyle (clamped to minimumWidth/minimumHeight)
++ * 2. The implicit size of the image overlay (if present)
++ * 3. Zero */
++ const qreal widthInStyle = qMax(m_delegateProperties->minimumWidth(), m_delegateProperties->width());
++ const qreal heightInStyle = qMax(m_delegateProperties->minimumHeight(), m_delegateProperties->height());
++ setImplicitWidth(widthInStyle > 0 || !m_imageOverlay ? widthInStyle : m_imageOverlay->implicitWidth());
++ setImplicitHeight(heightInStyle > 0 || !m_imageOverlay ? heightInStyle : m_imageOverlay->implicitHeight());
++ }
++
++ void QQStyleKitDelegate::maybeCreateColor()
++ {
++ if (m_colorOverlay)
++ return;
++ if (!m_delegateProperties)
++ return;
++ if (m_delegateProperties->color().alpha() == 0
++ && (m_delegateProperties->border()->color().alpha() == 0
++ || m_delegateProperties->border()->width() == 0)) {
++ // Lazy-create the color rectangle later, if/when needed
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::colorChanged,
++ this, &QQStyleKitDelegate::maybeCreateColor, Qt::UniqueConnection);
++ connect(m_delegateProperties->border(), &QQStyleKitBorderProperties::colorChanged,
++ this, &QQStyleKitDelegate::maybeCreateColor, Qt::UniqueConnection);
++ connect(m_delegateProperties->border(), &QQStyleKitBorderProperties::widthChanged,
++ this, &QQStyleKitDelegate::maybeCreateColor, Qt::UniqueConnection);
++ return;
++ }
++
++ disconnect(m_delegateProperties, &QQStyleKitDelegateProperties::colorChanged,
++ this, &QQStyleKitDelegate::maybeCreateColor);
++ disconnect(m_delegateProperties->border(), &QQStyleKitBorderProperties::colorChanged,
++ this, &QQStyleKitDelegate::maybeCreateColor);
++ disconnect(m_delegateProperties->border(), &QQStyleKitBorderProperties::widthChanged,
++ this, &QQStyleKitDelegate::maybeCreateColor);
++
++ QQmlEngine *engine = qmlEngine(this);
++ Q_ASSERT(engine);
++ static QQmlComponent *component = nullptr;
++ if (!component || component->engine() != engine) {
++ delete component;
++ component = new QQmlComponent(engine);
++ const auto qmlCode = R"(
++ import QtQuick
++ Rectangle {
++ z: -3
++ width: parent.width
++ height: parent.height
++ color: delegateStyle.color
++ opacity: delegateStyle.opacity
++ topLeftRadius: delegateStyle.topLeftRadius
++ topRightRadius: delegateStyle.topRightRadius
++ bottomLeftRadius: delegateStyle.bottomLeftRadius
++ bottomRightRadius: delegateStyle.bottomRightRadius
++ border.width: delegateStyle.border.width
++ border.color: delegateStyle.border.color
++ }
++ )"_ba;
++ component->setData(qmlCode, QUrl());
++ Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
++ }
++
++ QQmlContext *ctx = QQmlEngine::contextForObject(this);
++ m_colorOverlay = qobject_cast<QQuickItem*>(component->beginCreate(ctx));
++ Q_ASSERT(m_colorOverlay);
++ m_colorOverlay->setParent(this);
++ m_colorOverlay->setParentItem(this);
++ component->completeCreate();
++ }
++
++ void QQStyleKitDelegate::maybeCreateGradient()
++ {
++ /* Unlike a Rectangle, a StyledItem draws both the color and the gradient at
++ * the same time. This allows a style to define them independently. That way you can
++ * define a common semi-transparent grayscale gradient once for a delegate in the style
++ * (e.g for control.background.gradient), and then tint it with different colors for
++ * different controls, states, or themes (e.g for button.hovered.background.color). */
++ if (m_gradientOverlay)
++ return;
++ if (!m_delegateProperties)
++ return;
++ if (!m_delegateProperties->gradient()) {
++ connect(m_delegateProperties, &QQStyleKitDelegateProperties::gradientChanged,
++ this, &QQStyleKitDelegate::maybeCreateGradient, Qt::UniqueConnection);
++ return;
++ }
++
++ disconnect(m_delegateProperties, &QQStyleKitDelegateProperties::gradientChanged,
++ this, &QQStyleKitDelegate::maybeCreateGradient);
++
++ QQmlEngine *engine = qmlEngine(this);
++ Q_ASSERT(engine);
++ static QQmlComponent *component = nullptr;
++ if (!component || component->engine() != engine) {
++ delete component;
++ component = new QQmlComponent(engine);
++ const auto qmlCode = R"(
++ import QtQuick
++ Rectangle {
++ z: -2
++ width: parent.width
++ height: parent.height
++ color: "transparent"
++ visible: delegateStyle.gradient != null
++ gradient: delegateStyle.gradient
++ topLeftRadius: delegateStyle.topLeftRadius
++ topRightRadius: delegateStyle.topRightRadius
++ bottomLeftRadius: delegateStyle.bottomLeftRadius
++ bottomRightRadius: delegateStyle.bottomRightRadius
++ border.width: delegateStyle.border.width
++ border.color: delegateStyle.border.color
++ }
++ )"_ba;
++ component->setData(qmlCode, QUrl());
++ Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
++ }
++
++ QQmlContext *ctx = QQmlEngine::contextForObject(this);
++ m_gradientOverlay = qobject_cast<QQuickItem*>(component->beginCreate(ctx));
++ Q_ASSERT(m_gradientOverlay);
++ m_gradientOverlay->setParent(this);
++ m_gradientOverlay->setParentItem(this);
++ component->completeCreate();
++ }
++
++ void QQStyleKitDelegate::maybeCreateImage()
++ {
++ if (m_imageOverlay)
++ return;
++ if (!m_delegateProperties)
++ return;
++ if (m_delegateProperties->image()->source().isEmpty()
++ || m_delegateProperties->image()->color().alpha() == 0) {
++ connect(m_delegateProperties->image(), &QQStyleKitImageProperties::sourceChanged,
++ this, &QQStyleKitDelegate::maybeCreateImage, Qt::UniqueConnection);
++ connect(m_delegateProperties->image(), &QQStyleKitImageProperties::colorChanged,
++ this, &QQStyleKitDelegate::maybeCreateImage, Qt::UniqueConnection);
++ return;
++ }
++
++ disconnect(m_delegateProperties->image(), &QQStyleKitImageProperties::sourceChanged,
++ this, &QQStyleKitDelegate::maybeCreateImage);
++ disconnect(m_delegateProperties->image(), &QQStyleKitImageProperties::colorChanged,
++ this, &QQStyleKitDelegate::maybeCreateImage);
++
++ QQmlEngine *engine = qmlEngine(this);
++ Q_ASSERT(engine);
++ static QQmlComponent *component = nullptr;
++ if (!component || component->engine() != engine) {
++ delete component;
++ component = new QQmlComponent(engine);
++ const auto qmlCode = R"(
++ import QtQuick.Controls.impl
++ ColorImage {
++ z: -1
++ width: parent.width
++ height: parent.height
++ color: delegateStyle.image.color
++ source: delegateStyle.image.source
++ fillMode: delegateStyle.image.fillMode
++ }
++ )"_ba;
++ component->setData(qmlCode, QUrl());
++ Q_ASSERT_X(!component->isError(), __FUNCTION__, component->errorString().toUtf8().constData());
++ }
++
++ QQmlContext *ctx = QQmlEngine::contextForObject(this);
++ m_imageOverlay = qobject_cast<QQuickItem*>(component->beginCreate(ctx));
++ m_imageOverlay->setParent(this);
++ m_imageOverlay->setParentItem(this);
++ component->completeCreate();
++
++ updateImplicitSize();
++ connect(m_imageOverlay, &QQuickItem::implicitWidthChanged, this, &QQStyleKitDelegate::updateImplicitSize);
++ connect(m_imageOverlay, &QQuickItem::implicitHeightChanged, this, &QQStyleKitDelegate::updateImplicitSize);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqstylekitdelegate_p.cpp"