aboutsummaryrefslogtreecommitdiffstats
path: root/src/effects/qquickrectangularshadow.cpp
diff options
context:
space:
mode:
authorKaj Grönholm <kaj.gronholm@qt.io>2025-10-13 16:56:01 +0300
committerKaj Grönholm <kaj.gronholm@qt.io>2025-10-23 18:41:34 +0300
commite36915ff4dff2909df82a657a49282d7b706de22 (patch)
tree334ad61d8a131c858ef6405c964e9a6b71eb3a68 /src/effects/qquickrectangularshadow.cpp
parent2dc6093edb87aa535f1dac47b515083dffa0d65d (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.cpp292
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