diff options
| author | Kaj Grönholm <kaj.gronholm@qt.io> | 2025-10-13 16:56:01 +0300 |
|---|---|---|
| committer | Kaj Grönholm <kaj.gronholm@qt.io> | 2025-10-23 18:41:34 +0300 |
| commit | e36915ff4dff2909df82a657a49282d7b706de22 (patch) | |
| tree | 334ad61d8a131c858ef6405c964e9a6b71eb3a68 /src/effects/qquickrectangularshadow.cpp | |
| parent | 2dc6093edb87aa535f1dac47b515083dffa0d65d (diff) | |
RectangularShadow: Add support for individual corner radius
Add API for specifying individual corner radius values similar to Quick
Rectangle. Try to keep the overhead minimum when not used, by storing
data in lazily allocated extra and using separate shader.
Use the API in the neumorphicpanel example.
Task-number: QTBUG-141110
Change-Id: I90e558526dff01c88fb2d163a33704b8f73aa1fc
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Diffstat (limited to 'src/effects/qquickrectangularshadow.cpp')
| -rw-r--r-- | src/effects/qquickrectangularshadow.cpp | 292 |
1 files changed, 282 insertions, 10 deletions
diff --git a/src/effects/qquickrectangularshadow.cpp b/src/effects/qquickrectangularshadow.cpp index d37f3d6cff..ea5b539d94 100644 --- a/src/effects/qquickrectangularshadow.cpp +++ b/src/effects/qquickrectangularshadow.cpp @@ -264,6 +264,7 @@ void QQuickRectangularShadow::setRadius(qreal radius) return; d->m_radius = radius; + d->maybeSetImplicitAntialiasing(); d->updateSizeProperties(); update(); Q_EMIT radiusChanged(); @@ -346,7 +347,8 @@ void QQuickRectangularShadow::setCached(bool cached) RectangularShadow item, due to \l blur, \l offset and \l spread. The material can also be replaced with a custom one. The default material - is a \l ShaderEffect with the following \l {ShaderEffect::}{fragmentShader}: + when not using individual corner radius is a \l ShaderEffect with the + following \l {ShaderEffect::}{fragmentShader}: \badcode #version 440 @@ -377,6 +379,10 @@ void QQuickRectangularShadow::setCached(bool cached) } \endcode + When using individual corner radius, the value of "float radius" uniform + is < 0 and there is an additional uniform "vec4 radius4". The corner radius + values order in the vec4 is top-left, top-right, bottom-left, bottom-right. + Qt Quick Effect Maker contains the RectangularShadow node that can be used as a starting point for a custom material. You can directly use the exported effect containing that node as a RectangularShadow material. @@ -414,6 +420,214 @@ void QQuickRectangularShadow::setMaterial(QQuickItem *item) Q_EMIT materialChanged(); } +/*! + \since 6.11 + \qmlproperty real QtQuick.Effects::RectangularShadow::topLeftRadius + This property holds the radius used to draw the top left corner. + + If \l topLeftRadius is not set, \l radius will be used instead. + If \l topLeftRadius is zero, the corner will be sharp. + + \sa radius, topRightRadius, bottomLeftRadius, bottomRightRadius +*/ +qreal QQuickRectangularShadow::topLeftRadius() const +{ + Q_D(const QQuickRectangularShadow); + if (d->extra.isAllocated() && d->extra->isTopLeftRadiusSet) + return d->extra->topLeftRadius; + return d->m_radius; +} + +void QQuickRectangularShadow::setTopLeftRadius(qreal radius) +{ + Q_D(QQuickRectangularShadow); + if (d->extra.isAllocated() + && d->extra->topLeftRadius == radius + && d->extra->isTopLeftRadiusSet) { + return; + } + + d->extra.value().topLeftRadius = radius; + d->extra.value().isTopLeftRadiusSet = true; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit topLeftRadiusChanged(); +} + +void QQuickRectangularShadow::resetTopLeftRadius() +{ + Q_D(QQuickRectangularShadow); + if (!d->extra.isAllocated()) + return; + if (!d->extra->isTopLeftRadiusSet) + return; + + d->extra->isTopLeftRadiusSet = false; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit topLeftRadiusChanged(); +} + +/*! + \since 6.11 + \qmlproperty real QtQuick.Effects::RectangularShadow::topRightRadius + This property holds the radius used to draw the top right corner. + + If \l topRightRadius is not set, \l radius will be used instead. + If \l topRightRadius is zero, the corner will be sharp. + + \sa radius, topLeftRadius, bottomLeftRadius, bottomRightRadius +*/ +qreal QQuickRectangularShadow::topRightRadius() const +{ + Q_D(const QQuickRectangularShadow); + if (d->extra.isAllocated() && d->extra->isTopRightRadiusSet) + return d->extra->topRightRadius; + return d->m_radius; +} + +void QQuickRectangularShadow::setTopRightRadius(qreal radius) +{ + Q_D(QQuickRectangularShadow); + if (d->extra.isAllocated() + && d->extra->topRightRadius == radius + && d->extra->isTopRightRadiusSet) { + return; + } + + d->extra.value().topRightRadius = radius; + d->extra.value().isTopRightRadiusSet = true; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit topRightRadiusChanged(); +} + +void QQuickRectangularShadow::resetTopRightRadius() +{ + Q_D(QQuickRectangularShadow); + if (!d->extra.isAllocated()) + return; + if (!d->extra.value().isTopRightRadiusSet) + return; + + d->extra->isTopRightRadiusSet = false; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit topRightRadiusChanged(); +} + +/*! + \since 6.11 + \qmlproperty real QtQuick.Effects::RectangularShadow::bottomLeftRadius + This property holds the radius used to draw the bottom left corner. + + If \l bottomLeftRadius is not set, \l radius will be used instead. + If \l bottomLeftRadius is zero, the corner will be sharp. + + \sa radius, topLeftRadius, topRightRadius, bottomRightRadius +*/ +qreal QQuickRectangularShadow::bottomLeftRadius() const +{ + Q_D(const QQuickRectangularShadow); + if (d->extra.isAllocated() && d->extra->isBottomLeftRadiusSet) + return d->extra->bottomLeftRadius; + return d->m_radius; +} + +void QQuickRectangularShadow::setBottomLeftRadius(qreal radius) +{ + Q_D(QQuickRectangularShadow); + if (d->extra.isAllocated() + && d->extra->bottomLeftRadius == radius + && d->extra->isBottomLeftRadiusSet) { + return; + } + + d->extra.value().bottomLeftRadius = radius; + d->extra.value().isBottomLeftRadiusSet = true; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit bottomLeftRadiusChanged(); +} + +void QQuickRectangularShadow::resetBottomLeftRadius() +{ + Q_D(QQuickRectangularShadow); + if (!d->extra.isAllocated()) + return; + if (!d->extra.value().isBottomLeftRadiusSet) + return; + + d->extra->isBottomLeftRadiusSet = false; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit bottomLeftRadiusChanged(); +} + +/*! + \since 6.11 + \qmlproperty real QtQuick.Effects::RectangularShadow::bottomRightRadius + This property holds the radius used to draw the bottom right corner. + + If \l bottomRightRadius is not set, \l radius will be used instead. + If \l bottomRightRadius is zero, the corner will be sharp. + + \sa radius, topLeftRadius, topRightRadius, bottomLeftRadius +*/ +qreal QQuickRectangularShadow::bottomRightRadius() const +{ + Q_D(const QQuickRectangularShadow); + if (d->extra.isAllocated() && d->extra->isBottomRightRadiusSet) + return d->extra->bottomRightRadius; + return d->m_radius; +} + +void QQuickRectangularShadow::setBottomRightRadius(qreal radius) +{ + Q_D(QQuickRectangularShadow); + if (d->extra.isAllocated() + && d->extra->bottomRightRadius == radius + && d->extra->isBottomRightRadiusSet) { + return; + } + + d->extra.value().bottomRightRadius = radius; + d->extra.value().isBottomRightRadiusSet = true; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit bottomRightRadiusChanged(); +} + +void QQuickRectangularShadow::resetBottomRightRadius() +{ + Q_D(QQuickRectangularShadow); + if (!d->extra.isAllocated()) + return; + if (!d->extra.value().isBottomRightRadiusSet) + return; + + d->extra->isBottomRightRadiusSet = false; + d->maybeSetImplicitAntialiasing(); + + d->updateSizeProperties(); + update(); + emit bottomRightRadiusChanged(); +} + // *** protected *** void QQuickRectangularShadow::componentComplete() @@ -449,10 +663,22 @@ QQuickRectangularShadowPrivate::QQuickRectangularShadowPrivate() m_defaultMaterial->setProperty("iResolution", QVector3D()); m_defaultMaterial->setProperty("rectSize", QPointF()); m_defaultMaterial->setProperty("color", QColorConstants::Black); + // Add both radius variants, shader decides which one to use m_defaultMaterial->setProperty("radius", 0.0); + m_defaultMaterial->setProperty("radius4", QVector4D()); m_defaultMaterial->setProperty("blur", 10.0); } +void QQuickRectangularShadowPrivate::updateDefaultShader() +{ + QUrl fs; + if (useIndividualRadius()) + fs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow4r.frag.qsb")); + else + fs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.frag.qsb")); + m_defaultMaterial->setFragmentShader(fs); +} + void QQuickRectangularShadowPrivate::initialize() { Q_Q(QQuickRectangularShadow); @@ -470,8 +696,7 @@ void QQuickRectangularShadowPrivate::initialize() // Default to antialiased setImplicitAntialiasing(true); - QUrl fs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.frag.qsb")); - m_defaultMaterial->setFragmentShader(fs); + updateDefaultShader(); QUrl vs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.vert.qsb")); m_defaultMaterial->setVertexShader(vs); @@ -521,10 +746,13 @@ void QQuickRectangularShadowPrivate::updateShaderSource() void QQuickRectangularShadowPrivate::updateSizeProperties() { Q_Q(QQuickRectangularShadow); + + // Check if we should switch the shader + updateDefaultShader(); + auto *material = currentMaterial(); const qreal padding = getPadding(); - const qreal clampedRad = clampedRadius(); const qreal effectWidth = q->width() + padding; const qreal effectHeight = q->height() + padding; @@ -544,7 +772,14 @@ void QQuickRectangularShadowPrivate::updateSizeProperties() QPointF rectSize = QPointF((effectWidth * 0.5 - blurReduction), (effectHeight * 0.5 - blurReduction)); material->setProperty("rectSize", rectSize); - material->setProperty("radius", clampedRad); + if (useIndividualRadius()) { + const QVector4D clampedRad4 = clampedRadius4R(); + material->setProperty("radius4", clampedRad4); + material->setProperty("radius", -1); + } else { + const qreal clampedRad = clampedRadius(m_radius); + material->setProperty("radius", clampedRad); + } // Extend blur amount to match with how the CSS box-shadow blur behaves. // and to fully utilize the item size. const qreal shaderBlur = m_blur * 2.1 + aa; @@ -557,19 +792,19 @@ void QQuickRectangularShadowPrivate::updateCached() effectPrivate->layer()->setEnabled(m_cached); } -qreal QQuickRectangularShadowPrivate::clampedRadius() const +qreal QQuickRectangularShadowPrivate::clampedRadius(qreal radius) const { Q_Q(const QQuickRectangularShadow); qreal maxRadius = qMin(q->width(), q->height()) * 0.5; maxRadius += m_spread * 2; - qreal spreadRadius = m_radius + m_spread; - if (m_radius < m_spread && !qFuzzyIsNull(m_spread)) { + qreal spreadRadius = radius + m_spread; + if (radius < m_spread && !qFuzzyIsNull(m_spread)) { // CSS box-shadow has a specific math to calculate radius with spread // https://www.w3.org/TR/css-backgrounds-3/#shadow-shape // "the spread distance is first multiplied by the proportion 1 + (r-1)^3, // where r is the ratio of the border radius to the spread distance". - qreal r = (m_radius / m_spread) - 1; - spreadRadius = m_radius + m_spread * (1 + r * r * r); + qreal r = (radius / m_spread) - 1; + spreadRadius = radius + m_spread * (1 + r * r * r); } // Reduce the radius when the blur increases const qreal blurReduce = m_blur * 0.75; @@ -578,6 +813,17 @@ qreal QQuickRectangularShadowPrivate::clampedRadius() const return qMin(limitedRadius, maxRadius); } +QVector4D QQuickRectangularShadowPrivate::clampedRadius4R() const +{ + Q_Q(const QQuickRectangularShadow); + // Clamp each corner individually + qreal tl = clampedRadius(q->topLeftRadius()); + qreal tr = clampedRadius(q->topRightRadius()); + qreal bl = clampedRadius(q->bottomLeftRadius()); + qreal br = clampedRadius(q->bottomRightRadius()); + return QVector4D(tl, tr, bl, br); +} + QQuickItem *QQuickRectangularShadowPrivate::currentMaterial() const { if (m_material) @@ -586,4 +832,30 @@ QQuickItem *QQuickRectangularShadowPrivate::currentMaterial() const return m_defaultMaterial; } +void QQuickRectangularShadowPrivate::maybeSetImplicitAntialiasing() +{ + bool implicitAA = (m_radius > 0); + if (extra.isAllocated() && !implicitAA) { + const auto &e = extra.value(); + implicitAA = (e.isTopLeftRadiusSet && e.topLeftRadius > 0) + || (e.isTopRightRadiusSet && e.topRightRadius > 0) + || (e.isBottomLeftRadiusSet && e.bottomLeftRadius > 0) + || (e.isBottomRightRadiusSet && e.bottomRightRadius > 0); + } + setImplicitAntialiasing(implicitAA); +} + +bool QQuickRectangularShadowPrivate::useIndividualRadius() const +{ + bool individualRadius = false; + if (extra.isAllocated()) { + const auto &e = extra.value(); + individualRadius = e.isTopLeftRadiusSet + || e.isTopRightRadiusSet + || e.isBottomLeftRadiusSet + || e.isBottomRightRadiusSet; + } + return individualRadius; +} + QT_END_NAMESPACE |
