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 Since Qt 6.11, RectangularShadow also supports individual corner
80 radius values. When not needed, for best performance, set only the common
81 \l radius.
82 \endlist
83
84 Here is a table with screenshots to compare the rendering output of
85 RectangularShadow and CSS box-shadow in the Chrome browser. The right-most
86 element is RectangularShadow in blur multiplied with \c {1.2}
87 (so \c 24, \c 48, \c 48) for a closer match.
88
89 \table 80%
90 \header
91 \li type
92 \li CSS box-shadow
93 \li RectangularShadow
94 \li RectangularShadow + extra blur
95 \row
96 \li offset: (0, 0) \br
97 blur: 20 \br
98 spread: 0 \br
99 \li \image rectangularshadow-css-1.png
100 \li \image rectangularshadow-item-1.png
101 \li \image rectangularshadow-itemblur-1.png
102 \row
103 \li offset: (-10, -20) \br
104 blur: 40 \br
105 spread: 0 \br
106 \li \image rectangularshadow-css-2.png
107 \li \image rectangularshadow-item-2.png
108 \li \image rectangularshadow-itemblur-2.png
109 \row
110 \li offset: (-10, -20) \br
111 blur: 40 \br
112 spread: 10 \br
113 \li \image rectangularshadow-css-3.png
114 \li \image rectangularshadow-item-3.png
115 \li \image rectangularshadow-itemblur-3.png
116 \endtable
117
118
119 RectangularShadow extends the shadow size with an exact amount regarding
120 the blur amount, while some other shadows (including CSS box-shadow) have
121 a multiplier for the size. The size of the shadow item inside a
122 RectangularShadow is:
123 \badcode
124 width = rectangularShadow.width + 2 * blur + 2 * spread
125 height = rectangularShadow.height + 2 * blur + 2 * spread
126 \endcode
127
128 For example, the shadow item size of the code below is 280x180 pixels.
129 Radius or offset values do not affect the shadow item size.
130 \qml
131 RectangularShadow {
132 width: 200
133 height: 100
134 blur: 30
135 spread: 10
136 radius: 20
137 }
138 \endqml
139
140*/
141
142/*!
143 \qmlproperty bool QtQuick.Effects::RectangularShadow::antialiasing
144
145 Used to decide if the shadow should use antialiasing or not.
146 When this is \c true, a single pixel antialiasing is used even
147 when the \l blur is \c 0.
148
149 The default value is \c true.
150*/
151
152QQuickRectangularShadow::QQuickRectangularShadow(QQuickItem *parent)
153 : QQuickItem(*new QQuickRectangularShadowPrivate, parent)
154{
155 setFlag(ItemHasContents);
156}
157
158/*!
159 \qmlproperty vector2d QtQuick.Effects::RectangularShadow::offset
160
161 This property defines the position offset that is used for the shadow.
162 This offset is appended to the shadow position, relative to the
163 RectangularShadow item position.
164
165 The default value is \c {Qt.vector2d(0.0, 0.0)} (no offset).
166*/
167QVector2D QQuickRectangularShadow::offset() const
168{
169 Q_D(const QQuickRectangularShadow);
170 return d->m_offset;
171}
172
173void QQuickRectangularShadow::setOffset(const QVector2D &offset)
174{
175 Q_D(QQuickRectangularShadow);
176 if (offset == d->m_offset)
177 return;
178
179 d->m_offset = offset;
180 d->updateSizeProperties();
181 update();
182 Q_EMIT offsetChanged();
183}
184
185/*!
186 \qmlproperty color QtQuick.Effects::RectangularShadow::color
187
188 This property defines the RGBA color value that is used for the shadow.
189
190 The default value is \c {Qt.rgba(0.0, 0.0, 0.0, 1.0)} (black).
191*/
192QColor QQuickRectangularShadow::color() const
193{
194 Q_D(const QQuickRectangularShadow);
195 return d->m_color;
196}
197
198void QQuickRectangularShadow::setColor(const QColor &color)
199{
200 Q_D(QQuickRectangularShadow);
201 if (color == d->m_color)
202 return;
203
204 d->m_color = color;
205 d->updateColor();
206 update();
207 Q_EMIT colorChanged();
208}
209
210/*!
211 \qmlproperty real QtQuick.Effects::RectangularShadow::blur
212
213 This property defines how many pixels outside the item area are reached
214 by the shadow.
215
216 The value ranges from 0.0 (no blur) to inf (infinite blur).
217 The default value is \c 10.0.
218
219 \note To match with the CSS box-shadow rendering output, the optimal blur
220 amount is something like: \c {1.2 * cssBlur}
221*/
222qreal QQuickRectangularShadow::blur() const
223{
224 Q_D(const QQuickRectangularShadow);
225 return d->m_blur;
226}
227
228void QQuickRectangularShadow::setBlur(qreal blur)
229{
230 Q_D(QQuickRectangularShadow);
231 blur = qMax(qreal(0), blur);
232 if (blur == d->m_blur)
233 return;
234
235 d->m_blur = blur;
236 d->updateSizeProperties();
237 update();
238 Q_EMIT blurChanged();
239}
240
241/*!
242 \qmlproperty real QtQuick.Effects::RectangularShadow::radius
243
244 This property defines the corner radius that is used to draw a shadow with
245 rounded corners.
246
247 The value ranges from 0.0 to half of the effective width or height of
248 the item, whichever is smaller.
249
250 The default value is \c 0.
251*/
252qreal QQuickRectangularShadow::radius() const
253{
254 Q_D(const QQuickRectangularShadow);
255 return d->m_radius;
256}
257
258void QQuickRectangularShadow::setRadius(qreal radius)
259{
260 Q_D(QQuickRectangularShadow);
261 radius = qMax(qreal(0), radius);
262 if (radius == d->m_radius)
263 return;
264
265 d->m_radius = radius;
266 d->maybeSetImplicitAntialiasing();
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 when not using individual corner radius is a \l ShaderEffect with the
350 following \l {ShaderEffect::}{fragmentShader}:
351
352 \badcode
353 #version 440
354
355 layout(location = 0) in vec2 texCoord;
356 layout(location = 1) in vec2 fragCoord;
357 layout(location = 0) out vec4 fragColor;
358
359 layout(std140, binding = 0) uniform buf {
360 mat4 qt_Matrix;
361 float qt_Opacity;
362 vec4 color;
363 vec3 iResolution;
364 vec2 rectSize;
365 float radius;
366 float blur;
367 };
368
369 float roundedBox(vec2 centerPos, vec2 size, float radii) {
370 return length(max(abs(centerPos) - size + radii, 0.0)) - radii;
371 }
372
373 void main()
374 {
375 float box = roundedBox(fragCoord - iResolution.xy * 0.5, rectSize, radius);
376 float a = 1.0 - smoothstep(0.0, blur, box);
377 fragColor = color * qt_Opacity * a * a;
378 }
379 \endcode
380
381 When using individual corner radius, the value of "float radius" uniform
382 is < 0 and there is an additional uniform "vec4 radius4". The corner radius
383 values order in the vec4 is top-left, top-right, bottom-left, bottom-right.
384
385 Qt Quick Effect Maker contains the RectangularShadow node that can be used
386 as a starting point for a custom material. You can directly use the exported
387 effect containing that node as a RectangularShadow material.
388 \qml
389 RectangularShadow {
390 ...
391 material: MyShadowEffect { }
392 }
393 \endqml
394
395 To return to use the default material, set the material property to \c null.
396*/
397QQuickItem *QQuickRectangularShadow::material() const
398{
399 Q_D(const QQuickRectangularShadow);
400 return d->currentMaterial();
401}
402
403void QQuickRectangularShadow::setMaterial(QQuickItem *item)
404{
405 Q_D(QQuickRectangularShadow);
406 if (item == d->m_material)
407 return;
408
409 if (item) {
410 item->setParentItem(this);
411 item->setZ(-1);
412 }
413 if (d->m_material)
414 d->m_material->setVisible(false);
415
416 d->m_material = item;
417 d->updateShaderSource();
418 update();
419 Q_EMIT materialChanged();
420}
421
422/*!
423 \since 6.11
424 \qmlproperty real QtQuick.Effects::RectangularShadow::topLeftRadius
425 This property holds the radius used to draw the top left corner.
426
427 If \l topLeftRadius is not set, \l radius will be used instead.
428 If \l topLeftRadius is zero, the corner will be sharp.
429
430 \sa radius, topRightRadius, bottomLeftRadius, bottomRightRadius
431*/
432qreal QQuickRectangularShadow::topLeftRadius() const
433{
434 Q_D(const QQuickRectangularShadow);
435 if (d->extra.isAllocated() && d->extra->isTopLeftRadiusSet)
436 return d->extra->topLeftRadius;
437 return d->m_radius;
438}
439
440void QQuickRectangularShadow::setTopLeftRadius(qreal radius)
441{
442 Q_D(QQuickRectangularShadow);
443 if (d->extra.isAllocated()
444 && d->extra->topLeftRadius == radius
445 && d->extra->isTopLeftRadiusSet) {
446 return;
447 }
448
449 d->extra.value().topLeftRadius = radius;
450 d->extra.value().isTopLeftRadiusSet = true;
451 d->maybeSetImplicitAntialiasing();
452
453 d->updateSizeProperties();
454 update();
455 emit topLeftRadiusChanged();
456}
457
458void QQuickRectangularShadow::resetTopLeftRadius()
459{
460 Q_D(QQuickRectangularShadow);
461 if (!d->extra.isAllocated())
462 return;
463 if (!d->extra->isTopLeftRadiusSet)
464 return;
465
466 d->extra->isTopLeftRadiusSet = false;
467 d->maybeSetImplicitAntialiasing();
468
469 d->updateSizeProperties();
470 update();
471 emit topLeftRadiusChanged();
472}
473
474/*!
475 \since 6.11
476 \qmlproperty real QtQuick.Effects::RectangularShadow::topRightRadius
477 This property holds the radius used to draw the top right corner.
478
479 If \l topRightRadius is not set, \l radius will be used instead.
480 If \l topRightRadius is zero, the corner will be sharp.
481
482 \sa radius, topLeftRadius, bottomLeftRadius, bottomRightRadius
483*/
484qreal QQuickRectangularShadow::topRightRadius() const
485{
486 Q_D(const QQuickRectangularShadow);
487 if (d->extra.isAllocated() && d->extra->isTopRightRadiusSet)
488 return d->extra->topRightRadius;
489 return d->m_radius;
490}
491
492void QQuickRectangularShadow::setTopRightRadius(qreal radius)
493{
494 Q_D(QQuickRectangularShadow);
495 if (d->extra.isAllocated()
496 && d->extra->topRightRadius == radius
497 && d->extra->isTopRightRadiusSet) {
498 return;
499 }
500
501 d->extra.value().topRightRadius = radius;
502 d->extra.value().isTopRightRadiusSet = true;
503 d->maybeSetImplicitAntialiasing();
504
505 d->updateSizeProperties();
506 update();
507 emit topRightRadiusChanged();
508}
509
510void QQuickRectangularShadow::resetTopRightRadius()
511{
512 Q_D(QQuickRectangularShadow);
513 if (!d->extra.isAllocated())
514 return;
515 if (!d->extra.value().isTopRightRadiusSet)
516 return;
517
518 d->extra->isTopRightRadiusSet = false;
519 d->maybeSetImplicitAntialiasing();
520
521 d->updateSizeProperties();
522 update();
523 emit topRightRadiusChanged();
524}
525
526/*!
527 \since 6.11
528 \qmlproperty real QtQuick.Effects::RectangularShadow::bottomLeftRadius
529 This property holds the radius used to draw the bottom left corner.
530
531 If \l bottomLeftRadius is not set, \l radius will be used instead.
532 If \l bottomLeftRadius is zero, the corner will be sharp.
533
534 \sa radius, topLeftRadius, topRightRadius, bottomRightRadius
535*/
536qreal QQuickRectangularShadow::bottomLeftRadius() const
537{
538 Q_D(const QQuickRectangularShadow);
539 if (d->extra.isAllocated() && d->extra->isBottomLeftRadiusSet)
540 return d->extra->bottomLeftRadius;
541 return d->m_radius;
542}
543
544void QQuickRectangularShadow::setBottomLeftRadius(qreal radius)
545{
546 Q_D(QQuickRectangularShadow);
547 if (d->extra.isAllocated()
548 && d->extra->bottomLeftRadius == radius
549 && d->extra->isBottomLeftRadiusSet) {
550 return;
551 }
552
553 d->extra.value().bottomLeftRadius = radius;
554 d->extra.value().isBottomLeftRadiusSet = true;
555 d->maybeSetImplicitAntialiasing();
556
557 d->updateSizeProperties();
558 update();
559 emit bottomLeftRadiusChanged();
560}
561
562void QQuickRectangularShadow::resetBottomLeftRadius()
563{
564 Q_D(QQuickRectangularShadow);
565 if (!d->extra.isAllocated())
566 return;
567 if (!d->extra.value().isBottomLeftRadiusSet)
568 return;
569
570 d->extra->isBottomLeftRadiusSet = false;
571 d->maybeSetImplicitAntialiasing();
572
573 d->updateSizeProperties();
574 update();
575 emit bottomLeftRadiusChanged();
576}
577
578/*!
579 \since 6.11
580 \qmlproperty real QtQuick.Effects::RectangularShadow::bottomRightRadius
581 This property holds the radius used to draw the bottom right corner.
582
583 If \l bottomRightRadius is not set, \l radius will be used instead.
584 If \l bottomRightRadius is zero, the corner will be sharp.
585
586 \sa radius, topLeftRadius, topRightRadius, bottomLeftRadius
587*/
588qreal QQuickRectangularShadow::bottomRightRadius() const
589{
590 Q_D(const QQuickRectangularShadow);
591 if (d->extra.isAllocated() && d->extra->isBottomRightRadiusSet)
592 return d->extra->bottomRightRadius;
593 return d->m_radius;
594}
595
596void QQuickRectangularShadow::setBottomRightRadius(qreal radius)
597{
598 Q_D(QQuickRectangularShadow);
599 if (d->extra.isAllocated()
600 && d->extra->bottomRightRadius == radius
601 && d->extra->isBottomRightRadiusSet) {
602 return;
603 }
604
605 d->extra.value().bottomRightRadius = radius;
606 d->extra.value().isBottomRightRadiusSet = true;
607 d->maybeSetImplicitAntialiasing();
608
609 d->updateSizeProperties();
610 update();
611 emit bottomRightRadiusChanged();
612}
613
614void QQuickRectangularShadow::resetBottomRightRadius()
615{
616 Q_D(QQuickRectangularShadow);
617 if (!d->extra.isAllocated())
618 return;
619 if (!d->extra.value().isBottomRightRadiusSet)
620 return;
621
622 d->extra->isBottomRightRadiusSet = false;
623 d->maybeSetImplicitAntialiasing();
624
625 d->updateSizeProperties();
626 update();
627 emit bottomRightRadiusChanged();
628}
629
630// *** protected ***
631
632void QQuickRectangularShadow::componentComplete()
633{
634 Q_D(QQuickRectangularShadow);
635 QQuickItem::componentComplete();
636 d->initialize();
637}
638
639void QQuickRectangularShadow::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
640{
641 Q_D(QQuickRectangularShadow);
642 QQuickItem::geometryChange(newGeometry, oldGeometry);
643 if (width() > 0 && height() > 0)
644 d->handleGeometryChange(newGeometry, oldGeometry);
645}
646
647void QQuickRectangularShadow::itemChange(ItemChange change, const ItemChangeData &value)
648{
649 Q_D(QQuickRectangularShadow);
650 d->handleItemChange(change, value);
651 QQuickItem::itemChange(change, value);
652}
653
654// *** private ***
655
656QQuickRectangularShadowPrivate::QQuickRectangularShadowPrivate()
657{
658 Q_Q(QQuickRectangularShadow);
659 m_defaultMaterial = new QQuickShaderEffect(q);
660 // Initial values to not get warnings of missing properties.
661 // Proper values are updated later.
662 m_defaultMaterial->setProperty("iResolution", QVector3D());
663 m_defaultMaterial->setProperty("rectSize", QPointF());
664 m_defaultMaterial->setProperty("color", QColorConstants::Black);
665 // Add both radius variants, shader decides which one to use
666 m_defaultMaterial->setProperty("radius", 0.0);
667 m_defaultMaterial->setProperty("radius4", QVector4D());
668 m_defaultMaterial->setProperty("blur", 10.0);
669}
670
671void QQuickRectangularShadowPrivate::updateDefaultShader()
672{
673 QUrl fs;
674 if (useIndividualRadius())
675 fs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow4r.frag.qsb"));
676 else
677 fs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.frag.qsb"));
678 m_defaultMaterial->setFragmentShader(fs);
679}
680
681void QQuickRectangularShadowPrivate::initialize()
682{
683 Q_Q(QQuickRectangularShadow);
684 if (m_initialized)
685 return;
686 if (!q->isComponentComplete())
687 return;
688 if (!q->window())
689 return;
690 if (q->width() <= 0 || q->height() <= 0)
691 return;
692
693 m_defaultMaterial->setParentItem(q);
694 m_defaultMaterial->setZ(-1);
695 // Default to antialiased
696 setImplicitAntialiasing(true);
697
698 updateDefaultShader();
699 QUrl vs = QUrl(QStringLiteral("qrc:/data/shaders/rectangularshadow.vert.qsb"));
700 m_defaultMaterial->setVertexShader(vs);
701
702 updateShaderSource();
703 m_initialized = true;
704}
705
706void QQuickRectangularShadowPrivate::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
707{
708 Q_UNUSED(value);
709 if (change == QQuickItem::ItemSceneChange)
710 initialize();
711}
712
713void QQuickRectangularShadowPrivate::handleGeometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
714{
715 Q_UNUSED(newGeometry);
716 Q_UNUSED(oldGeometry);
717 initialize();
718 updateSizeProperties();
719}
720
721qreal QQuickRectangularShadowPrivate::getPadding() const
722{
723 return qreal(m_blur * 2 + m_spread * 2);
724}
725
726void QQuickRectangularShadowPrivate::updateColor()
727{
728 currentMaterial()->setProperty("color", m_color);
729}
730
731void QQuickRectangularShadowPrivate::updateShaderSource()
732{
733 Q_Q(QQuickRectangularShadow);
734 if (!q->isComponentComplete())
735 return;
736
737 if (m_material)
738 m_defaultMaterial->setVisible(false);
739
740 updateSizeProperties();
741 updateColor();
742 currentMaterial()->setVisible(true);
743}
744
745void QQuickRectangularShadowPrivate::updateSizeProperties()
746{
747 Q_Q(QQuickRectangularShadow);
748
749 // Check if we should switch the shader
750 updateDefaultShader();
751
752 auto *material = currentMaterial();
753
754 const qreal padding = getPadding();
755 const qreal effectWidth = q->width() + padding;
756 const qreal effectHeight = q->height() + padding;
757
758 const qreal effectX = (q->width() - effectWidth) * 0.5 + m_offset.x();
759 const qreal effectY = (q->height() - effectHeight) * 0.5 + m_offset.y();
760 material->setX(effectX);
761 material->setY(effectY);
762 material->setWidth(effectWidth);
763 material->setHeight(effectHeight);
764
765 const qreal aa = q->antialiasing() ? 1.0 : 0.0;
766 material->setProperty("iResolution", QVector3D(effectWidth, effectHeight, 1.0));
767
768 // The shrinking ratio when the amount of blur increases
769 // so blur extends also towards inner direction.
770 const qreal blurReduction = m_blur * 1.8 + aa;
771 QPointF rectSize = QPointF((effectWidth * 0.5 - blurReduction),
772 (effectHeight * 0.5 - blurReduction));
773 material->setProperty("rectSize", rectSize);
774 if (useIndividualRadius()) {
775 const QVector4D clampedRad4 = clampedRadius4R();
776 material->setProperty("radius4", clampedRad4);
777 material->setProperty("radius", -1);
778 } else {
779 const qreal clampedRad = clampedRadius(m_radius);
780 material->setProperty("radius", clampedRad);
781 }
782 // Extend blur amount to match with how the CSS box-shadow blur behaves.
783 // and to fully utilize the item size.
784 const qreal shaderBlur = m_blur * 2.1 + aa;
785 material->setProperty("blur", shaderBlur);
786}
787
788void QQuickRectangularShadowPrivate::updateCached()
789{
790 QQuickItemPrivate *effectPrivate = QQuickItemPrivate::get(currentMaterial());
791 effectPrivate->layer()->setEnabled(m_cached);
792}
793
794qreal QQuickRectangularShadowPrivate::clampedRadius(qreal radius) const
795{
796 Q_Q(const QQuickRectangularShadow);
797 qreal maxRadius = qMin(q->width(), q->height()) * 0.5;
798 maxRadius += m_spread * 2;
799 qreal spreadRadius = radius + m_spread;
800 if (radius < m_spread && !qFuzzyIsNull(m_spread)) {
801 // CSS box-shadow has a specific math to calculate radius with spread
802 // https://www.w3.org/TR/css-backgrounds-3/#shadow-shape
803 // "the spread distance is first multiplied by the proportion 1 + (r-1)^3,
804 // where r is the ratio of the border radius to the spread distance".
805 qreal r = (radius / m_spread) - 1;
806 spreadRadius = radius + m_spread * (1 + r * r * r);
807 }
808 // Reduce the radius when the blur increases
809 const qreal blurReduce = m_blur * 0.75;
810 maxRadius -= blurReduce;
811 const qreal limitedRadius = qMax(0.0, spreadRadius - blurReduce);
812 return qMin(limitedRadius, maxRadius);
813}
814
815QVector4D QQuickRectangularShadowPrivate::clampedRadius4R() const
816{
817 Q_Q(const QQuickRectangularShadow);
818 // Clamp each corner individually
819 qreal tl = clampedRadius(q->topLeftRadius());
820 qreal tr = clampedRadius(q->topRightRadius());
821 qreal bl = clampedRadius(q->bottomLeftRadius());
822 qreal br = clampedRadius(q->bottomRightRadius());
823 return QVector4D(tl, tr, bl, br);
824}
825
826QQuickItem *QQuickRectangularShadowPrivate::currentMaterial() const
827{
828 if (m_material)
829 return m_material;
830 else
831 return m_defaultMaterial;
832}
833
834void QQuickRectangularShadowPrivate::maybeSetImplicitAntialiasing()
835{
836 bool implicitAA = (m_radius > 0);
837 if (extra.isAllocated() && !implicitAA) {
838 const auto &e = extra.value();
839 implicitAA = (e.isTopLeftRadiusSet && e.topLeftRadius > 0)
840 || (e.isTopRightRadiusSet && e.topRightRadius > 0)
841 || (e.isBottomLeftRadiusSet && e.bottomLeftRadius > 0)
842 || (e.isBottomRightRadiusSet && e.bottomRightRadius > 0);
843 }
844 setImplicitAntialiasing(implicitAA);
845}
846
847bool QQuickRectangularShadowPrivate::useIndividualRadius() const
848{
849 bool individualRadius = false;
850 if (extra.isAllocated()) {
851 const auto &e = extra.value();
852 individualRadius = e.isTopLeftRadiusSet
853 || e.isTopRightRadiusSet
854 || e.isBottomLeftRadiusSet
855 || e.isBottomRightRadiusSet;
856 }
857 return individualRadius;
858}
859
860QT_END_NAMESPACE
Combined button and popup list for selecting options.