Revision control
Copy as Markdown
// 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"