Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickrectangularshadow.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtypes.h"
5#include <private/qquickrectangularshadow_p_p.h>
6#include <private/qquickshadereffect_p.h>
7#include <private/qquickitem_p.h>
8#include <QtCore/qurl.h>
9#include <QtGui/qvector4d.h>
10
12
13/*!
14 \qmltype RectangularShadow
15 \inqmlmodule QtQuick.Effects
16 \inherits Item
17 \ingroup qtquick-effects
18 \brief Creates smoothed rectangle, suitable for example for
19 shadow and glow effects.
20
21 RectangularShadow is a rounded rectangle with blur applied.
22 The performance of RectangularShadow is much better than a general
23 one that creates blurred shadow/glow of any shaped item.
24
25 The following example shows how to add a shadow to a \l Rectangle:
26 \table 70%
27 \row
28 \li \image rectangularshadow-example-1.png
29 \li \qml
30 import QtQuick
31 import QtQuick.Effects
32
33 ...
34 RectangularShadow {
35 anchors.fill: myRectangle
36 offset.x: -10
37 offset.y: -5
38 radius: myRectangle.radius
39 blur: 30
40 spread: 10
41 color: Qt.darker(myRectangle.color, 1.6)
42 }
43 Rectangle {
44 id: myRectangle
45 anchors.centerIn: parent
46 width: 200
47 height: 100
48 radius: 50
49 color: "#c0d0f0"
50 }
51 \endqml
52 \endtable
53
54 The API of RectangularShadow is similar to CSS box-shadow, with color,
55 offset, spread, and blur values. Additionally, RectangularShadow API
56 contains:
57
58 \list
59 \li \c real \l radius: Controls the rounding radius applied to rectangle
60 corners. Compared to CSS box-shadow, which inherits radius from the parent
61 element, RectangularShadow expects user to set it. This allows you to use
62 different radiuses and move the RectangularShadow separately from its parent
63 item.
64 \li \c bool \l cached: Allows caching the blurred shadow texture. This
65 increases memory usage while potentially improving rendering performance,
66 especially with bigger shadows that don't change dynamically.
67 \li \c item \l material: Contains the ShaderEffect element of the
68 RectangularShadow for advanced use. This allows, for example, extending the
69 effect with a custom shader.
70 \endlist
71
72 The rendering output also matches the CSS box-shadow, with few notable
73 differences. These differences exist to make the RectangularShadow as
74 high-performance as possible.
75 \list
76 \li Blurring is calculated mathematically in the shader rather than using
77 Gaussian blur, which CSS box-shadow implementations often use. This makes
78 the shadow look slightly different, especially with larger blur values.
79 \li All the rectangle corners must have an even radius. When creating a
80 shadow for a \l Rectangle with different radiuses, select the best-matching
81 radius for the shadow or use an alternative shadow method, for example,
82 \l MultiEffect.
83 \endlist
84
85 Here is a table with screenshots to compare the rendering output of
86 RectangularShadow and CSS box-shadow in the Chrome browser. The right-most
87 element is RectangularShadow in blur multiplied with \c {1.2}
88 (so \c 24, \c 48, \c 48) for a closer match.
89
90 \table 80%
91 \header
92 \li type
93 \li CSS box-shadow
94 \li RectangularShadow
95 \li RectangularShadow + extra blur
96 \row
97 \li offset: (0, 0) \br
98 blur: 20 \br
99 spread: 0 \br
100 \li \image rectangularshadow-css-1.png
101 \li \image rectangularshadow-item-1.png
102 \li \image rectangularshadow-itemblur-1.png
103 \row
104 \li offset: (-10, -20) \br
105 blur: 40 \br
106 spread: 0 \br
107 \li \image rectangularshadow-css-2.png
108 \li \image rectangularshadow-item-2.png
109 \li \image rectangularshadow-itemblur-2.png
110 \row
111 \li offset: (-10, -20) \br
112 blur: 40 \br
113 spread: 10 \br
114 \li \image rectangularshadow-css-3.png
115 \li \image rectangularshadow-item-3.png
116 \li \image rectangularshadow-itemblur-3.png
117 \endtable
118
119
120 RectangularShadow extends the shadow size with an exact amount regarding
121 the blur amount, while some other shadows (including CSS box-shadow) have
122 a multiplier for the size. The size of the shadow item inside a
123 RectangularShadow is:
124 \badcode
125 width = rectangularShadow.width + 2 * blur + 2 * spread
126 height = rectangularShadow.height + 2 * blur + 2 * spread
127 \endcode
128
129 For example, the shadow item size of the code below is 280x180 pixels.
130 Radius or offset values do not affect the shadow item size.
131 \qml
132 RectangularShadow {
133 width: 200
134 height: 100
135 blur: 30
136 spread: 10
137 radius: 20
138 }
139 \endqml
140
141*/
142
143/*!
144 \qmlproperty bool QtQuick.Effects::RectangularShadow::antialiasing
145
146 Used to decide if the shadow should use antialiasing or not.
147 When this is \c true, a single pixel antialiasing is used even
148 when the \l blur is \c 0.
149
150 The default value is \c true.
151*/
152
153QQuickRectangularShadow::QQuickRectangularShadow(QQuickItem *parent)
154 : QQuickItem(*new QQuickRectangularShadowPrivate, parent)
155{
156 setFlag(ItemHasContents);
157}
158
159/*!
160 \qmlproperty vector2d QtQuick.Effects::RectangularShadow::offset
161
162 This property defines the position offset that is used for the shadow.
163 This offset is appended to the shadow position, relative to the
164 RectangularShadow item position.
165
166 The default value is \c {Qt.vector2d(0.0, 0.0)} (no offset).
167*/
168QVector2D QQuickRectangularShadow::offset() const
169{
170 Q_D(const QQuickRectangularShadow);
171 return d->m_offset;
172}
173
174void QQuickRectangularShadow::setOffset(const QVector2D &offset)
175{
176 Q_D(QQuickRectangularShadow);
177 if (offset == d->m_offset)
178 return;
179
180 d->m_offset = offset;
181 d->updateSizeProperties();
182 update();
183 Q_EMIT offsetChanged();
184}
185
186/*!
187 \qmlproperty color QtQuick.Effects::RectangularShadow::color
188
189 This property defines the RGBA color value that is used for the shadow.
190
191 The default value is \c {Qt.rgba(0.0, 0.0, 0.0, 1.0)} (black).
192*/
193QColor QQuickRectangularShadow::color() const
194{
195 Q_D(const QQuickRectangularShadow);
196 return d->m_color;
197}
198
199void QQuickRectangularShadow::setColor(const QColor &color)
200{
201 Q_D(QQuickRectangularShadow);
202 if (color == d->m_color)
203 return;
204
205 d->m_color = color;
206 d->updateColor();
207 update();
208 Q_EMIT colorChanged();
209}
210
211/*!
212 \qmlproperty real QtQuick.Effects::RectangularShadow::blur
213
214 This property defines how many pixels outside the item area are reached
215 by the shadow.
216
217 The value ranges from 0.0 (no blur) to inf (infinite blur).
218 The default value is \c 10.0.
219
220 \note To match with the CSS box-shadow rendering output, the optimal blur
221 amount is something like: \c {1.2 * cssBlur}
222*/
223qreal QQuickRectangularShadow::blur() const
224{
225 Q_D(const QQuickRectangularShadow);
226 return d->m_blur;
227}
228
229void QQuickRectangularShadow::setBlur(qreal blur)
230{
231 Q_D(QQuickRectangularShadow);
232 blur = qMax(qreal(0), blur);
233 if (blur == d->m_blur)
234 return;
235
236 d->m_blur = blur;
237 d->updateSizeProperties();
238 update();
239 Q_EMIT blurChanged();
240}
241
242/*!
243 \qmlproperty real QtQuick.Effects::RectangularShadow::radius
244
245 This property defines the corner radius that is used to draw a shadow with
246 rounded corners.
247
248 The value ranges from 0.0 to half of the effective width or height of
249 the item, whichever is smaller.
250
251 The default value is \c 0.
252*/
253qreal QQuickRectangularShadow::radius() const
254{
255 Q_D(const QQuickRectangularShadow);
256 return d->m_radius;
257}
258
259void QQuickRectangularShadow::setRadius(qreal radius)
260{
261 Q_D(QQuickRectangularShadow);
262 radius = qMax(qreal(0), radius);
263 if (radius == d->m_radius)
264 return;
265
266 d->m_radius = radius;
267 d->updateSizeProperties();
268 update();
269 Q_EMIT radiusChanged();
270}
271
272/*!
273 \qmlproperty real QtQuick.Effects::RectangularShadow::spread
274
275 This property defines how much the shadow is spread (extended) in
276 pixels. This spread is appended to the shadow size, relative to the
277 RectangularShadow item size.
278
279 The value ranges from -inf to inf. The default value is \c 0.0.
280
281 \note The radius behavior with spread matches to CSS box-shadow
282 standard. So when the spread is smaller than the radius, the
283 shadow radius grows by the amount of spread. When the spread grows
284 bigger, radius grows only partially. See \l
285 {https://www.w3.org/TR/css-backgrounds-3/#shadow-shape}.
286 If the shadow radius should grow in sync when the shadow grows (like
287 with the Firefox CSS box-shadow implementation), increase the
288 RectangularShadow \c width and \c height instead of using the \c spread.
289*/
290qreal QQuickRectangularShadow::spread() const
291{
292 Q_D(const QQuickRectangularShadow);
293 return d->m_spread;
294}
295
296void QQuickRectangularShadow::setSpread(qreal spread)
297{
298 Q_D(QQuickRectangularShadow);
299 if (spread == d->m_spread)
300 return;
301
302 d->m_spread = spread;
303 d->updateSizeProperties();
304 update();
305 Q_EMIT spreadChanged();
306}
307
308/*!
309 \qmlproperty bool QtQuick.Effects::RectangularShadow::cached
310 This property allows the effect output pixels to be cached in order to
311 improve the rendering performance.
312
313 Every time the effect properties are changed, the pixels in
314 the cache must be updated. Memory consumption is increased, because an
315 extra buffer of memory is required for storing the effect output.
316
317 It is recommended to disable the cache when the source or the effect
318 properties are animated.
319
320 The default value is \c false.
321*/
322bool QQuickRectangularShadow::isCached() const
323{
324 Q_D(const QQuickRectangularShadow);
325 return d->m_cached;
326}
327
328void QQuickRectangularShadow::setCached(bool cached)
329{
330 Q_D(QQuickRectangularShadow);
331 if (cached == d->m_cached)
332 return;
333
334 d->m_cached = cached;
335 d->updateCached();
336 update();
337 Q_EMIT cachedChanged();
338}
339
340/*!
341 \qmlproperty item QtQuick.Effects::RectangularShadow::material
342
343 This property contains the \l ShaderEffect item of the shadow. You can use
344 this property to visualize the reach of the shadow, because the effect item
345 often has different position and size than the
346 RectangularShadow item, due to \l blur, \l offset and \l spread.
347
348 The material can also be replaced with a custom one. The default material
349 is a \l ShaderEffect with the following \l {ShaderEffect::}{fragmentShader}:
350
351 \badcode
352 #version 440
353
354 layout(location = 0) in vec2 texCoord;
355 layout(location = 1) in vec2 fragCoord;
356 layout(location = 0) out vec4 fragColor;
357
358 layout(std140, binding = 0) uniform buf {
359 mat4 qt_Matrix;
360 float qt_Opacity;
361 vec4 color;
362 vec3 iResolution;
363 vec2 rectSize;
364 float radius;
365 float blur;
366 };
367
368 float roundedBox(vec2 centerPos, vec2 size, float radii) {
369 return length(max(abs(centerPos) - size + radii, 0.0)) - radii;
370 }
371
372 void main()
373 {
374 float box = roundedBox(fragCoord - iResolution.xy * 0.5, rectSize, radius);
375 float a = 1.0 - smoothstep(0.0, blur, box);
376 fragColor = color * qt_Opacity * a * a;
377 }
378 \endcode
379
380 Qt Quick Effect Maker contains the RectangularShadow node that can be used
381 as a starting point for a custom material. You can directly use the exported
382 effect containing that node as a RectangularShadow material.
383 \qml
384 RectangularShadow {
385 ...
386 material: MyShadowEffect { }
387 }
388 \endqml
389
390 To return to use the default material, set the material property to \c null.
391*/
392QQuickItem *QQuickRectangularShadow::material() const
393{
394 Q_D(const QQuickRectangularShadow);
395 return d->currentMaterial();
396}
397
398void QQuickRectangularShadow::setMaterial(QQuickItem *item)
399{
400 Q_D(QQuickRectangularShadow);
401 if (item == d->m_material)
402 return;
403
404 if (item) {
405 item->setParentItem(this);
406 item->setZ(-1);
407 }
408 if (d->m_material)
409 d->m_material->setVisible(false);
410
411 d->m_material = item;
412 d->updateShaderSource();
413 update();
414 Q_EMIT materialChanged();
415}
416
417// *** protected ***
418
419void QQuickRectangularShadow::componentComplete()
420{
421 Q_D(QQuickRectangularShadow);
422 QQuickItem::componentComplete();
423 d->initialize();
424}
425
426void QQuickRectangularShadow::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
427{
428 Q_D(QQuickRectangularShadow);
429 QQuickItem::geometryChange(newGeometry, oldGeometry);
430 if (width() > 0 && height() > 0)
431 d->handleGeometryChange(newGeometry, oldGeometry);
432}
433
434void QQuickRectangularShadow::itemChange(ItemChange change, const ItemChangeData &value)
435{
436 Q_D(QQuickRectangularShadow);
437 d->handleItemChange(change, value);
438 QQuickItem::itemChange(change, value);
439}
440
441// *** private ***
442
443QQuickRectangularShadowPrivate::QQuickRectangularShadowPrivate()
444{
445 Q_Q(QQuickRectangularShadow);
446 m_defaultMaterial = new QQuickShaderEffect(q);
447 // Initial values to not get warnings of missing properties.
448 // Proper values are updated later.
449 m_defaultMaterial->setProperty("iResolution", QVector3D());
450 m_defaultMaterial->setProperty("rectSize", QPointF());
451 m_defaultMaterial->setProperty("color", QColorConstants::Black);
452 m_defaultMaterial->setProperty("radius", 0.0);
453 m_defaultMaterial->setProperty("blur", 10.0);
454}
455
456void QQuickRectangularShadowPrivate::initialize()
457{
458 Q_Q(QQuickRectangularShadow);
459 if (m_initialized)
460 return;
461 if (!q->isComponentComplete())
462 return;
463 if (!q->window())
464 return;
465 if (q->width() <= 0 || q->height() <= 0)
466 return;
467
468 m_defaultMaterial->setParentItem(q);
469 m_defaultMaterial->setZ(-1);
470 // Default to antialiased
471 setImplicitAntialiasing(true);
472
473 QUrl fs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.frag.qsb"));
474 m_defaultMaterial->setFragmentShader(fs);
475 QUrl vs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.vert.qsb"));
476 m_defaultMaterial->setVertexShader(vs);
477
478 updateShaderSource();
479 m_initialized = true;
480}
481
482void QQuickRectangularShadowPrivate::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
483{
484 Q_UNUSED(value);
485 if (change == QQuickItem::ItemSceneChange)
486 initialize();
487}
488
489void QQuickRectangularShadowPrivate::handleGeometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
490{
491 Q_UNUSED(newGeometry);
492 Q_UNUSED(oldGeometry);
493 initialize();
494 updateSizeProperties();
495}
496
497qreal QQuickRectangularShadowPrivate::getPadding() const
498{
499 return qreal(m_blur * 2 + m_spread * 2);
500}
501
502void QQuickRectangularShadowPrivate::updateColor()
503{
504 currentMaterial()->setProperty("color", m_color);
505}
506
507void QQuickRectangularShadowPrivate::updateShaderSource()
508{
509 Q_Q(QQuickRectangularShadow);
510 if (!q->isComponentComplete())
511 return;
512
513 if (m_material)
514 m_defaultMaterial->setVisible(false);
515
516 updateSizeProperties();
517 updateColor();
518 currentMaterial()->setVisible(true);
519}
520
521void QQuickRectangularShadowPrivate::updateSizeProperties()
522{
523 Q_Q(QQuickRectangularShadow);
524 auto *material = currentMaterial();
525
526 const qreal padding = getPadding();
527 const qreal clampedRad = clampedRadius();
528 const qreal effectWidth = q->width() + padding;
529 const qreal effectHeight = q->height() + padding;
530
531 const qreal effectX = (q->width() - effectWidth) * 0.5 + m_offset.x();
532 const qreal effectY = (q->height() - effectHeight) * 0.5 + m_offset.y();
533 material->setX(effectX);
534 material->setY(effectY);
535 material->setWidth(effectWidth);
536 material->setHeight(effectHeight);
537
538 const qreal aa = q->antialiasing() ? 1.0 : 0.0;
539 material->setProperty("iResolution", QVector3D(effectWidth, effectHeight, 1.0));
540
541 // The shrinking ratio when the amount of blur increases
542 // so blur extends also towards inner direction.
543 const qreal blurReduction = m_blur * 1.8 + aa;
544 QPointF rectSize = QPointF((effectWidth * 0.5 - blurReduction),
545 (effectHeight * 0.5 - blurReduction));
546 material->setProperty("rectSize", rectSize);
547 material->setProperty("radius", clampedRad);
548 // Extend blur amount to match with how the CSS box-shadow blur behaves.
549 // and to fully utilize the item size.
550 const qreal shaderBlur = m_blur * 2.1 + aa;
551 material->setProperty("blur", shaderBlur);
552}
553
554void QQuickRectangularShadowPrivate::updateCached()
555{
556 QQuickItemPrivate *effectPrivate = QQuickItemPrivate::get(currentMaterial());
557 effectPrivate->layer()->setEnabled(m_cached);
558}
559
560qreal QQuickRectangularShadowPrivate::clampedRadius() const
561{
562 Q_Q(const QQuickRectangularShadow);
563 qreal maxRadius = qMin(q->width(), q->height()) * 0.5;
564 maxRadius += m_spread * 2;
565 qreal spreadRadius = m_radius + m_spread;
566 if (m_radius < m_spread && !qFuzzyIsNull(m_spread)) {
567 // CSS box-shadow has a specific math to calculate radius with spread
568 // https://www.w3.org/TR/css-backgrounds-3/#shadow-shape
569 // "the spread distance is first multiplied by the proportion 1 + (r-1)^3,
570 // where r is the ratio of the border radius to the spread distance".
571 qreal r = (m_radius / m_spread) - 1;
572 spreadRadius = m_radius + m_spread * (1 + r * r * r);
573 }
574 // Reduce the radius when the blur increases
575 const qreal blurReduce = m_blur * 0.75;
576 maxRadius -= blurReduce;
577 const qreal limitedRadius = qMax(0.0, spreadRadius - blurReduce);
578 return qMin(limitedRadius, maxRadius);
579}
580
581QQuickItem *QQuickRectangularShadowPrivate::currentMaterial() const
582{
583 if (m_material)
584 return m_material;
585 else
586 return m_defaultMaterial;
587}
588
589QT_END_NAMESPACE