| 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 | |
| 11 | QT_BEGIN_NAMESPACE |
| 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 | |
| 153 | QQuickRectangularShadow::QQuickRectangularShadow(QQuickItem *parent) |
| 154 | : QQuickItem(*new QQuickRectangularShadowPrivate, parent) |
| 155 | { |
| 156 | setFlag(flag: 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 | */ |
| 168 | QVector2D QQuickRectangularShadow::offset() const |
| 169 | { |
| 170 | Q_D(const QQuickRectangularShadow); |
| 171 | return d->m_offset; |
| 172 | } |
| 173 | |
| 174 | void 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 | */ |
| 193 | QColor QQuickRectangularShadow::color() const |
| 194 | { |
| 195 | Q_D(const QQuickRectangularShadow); |
| 196 | return d->m_color; |
| 197 | } |
| 198 | |
| 199 | void 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 | */ |
| 223 | qreal QQuickRectangularShadow::blur() const |
| 224 | { |
| 225 | Q_D(const QQuickRectangularShadow); |
| 226 | return d->m_blur; |
| 227 | } |
| 228 | |
| 229 | void QQuickRectangularShadow::setBlur(qreal blur) |
| 230 | { |
| 231 | Q_D(QQuickRectangularShadow); |
| 232 | blur = qMax(a: qreal(0), b: 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 | */ |
| 253 | qreal QQuickRectangularShadow::radius() const |
| 254 | { |
| 255 | Q_D(const QQuickRectangularShadow); |
| 256 | return d->m_radius; |
| 257 | } |
| 258 | |
| 259 | void QQuickRectangularShadow::setRadius(qreal radius) |
| 260 | { |
| 261 | Q_D(QQuickRectangularShadow); |
| 262 | radius = qMax(a: qreal(0), b: 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 | */ |
| 290 | qreal QQuickRectangularShadow::spread() const |
| 291 | { |
| 292 | Q_D(const QQuickRectangularShadow); |
| 293 | return d->m_spread; |
| 294 | } |
| 295 | |
| 296 | void 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 | */ |
| 322 | bool QQuickRectangularShadow::isCached() const |
| 323 | { |
| 324 | Q_D(const QQuickRectangularShadow); |
| 325 | return d->m_cached; |
| 326 | } |
| 327 | |
| 328 | void 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 | */ |
| 392 | QQuickItem *QQuickRectangularShadow::material() const |
| 393 | { |
| 394 | Q_D(const QQuickRectangularShadow); |
| 395 | return d->currentMaterial(); |
| 396 | } |
| 397 | |
| 398 | void 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 | |
| 419 | void QQuickRectangularShadow::componentComplete() |
| 420 | { |
| 421 | Q_D(QQuickRectangularShadow); |
| 422 | QQuickItem::componentComplete(); |
| 423 | d->initialize(); |
| 424 | } |
| 425 | |
| 426 | void 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 | |
| 434 | void 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 | |
| 443 | QQuickRectangularShadowPrivate::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(name: "iResolution" , value: QVector3D()); |
| 450 | m_defaultMaterial->setProperty(name: "rectSize" , value: QPointF()); |
| 451 | m_defaultMaterial->setProperty(name: "color" , value: QColorConstants::Black); |
| 452 | m_defaultMaterial->setProperty(name: "radius" , value: 0.0); |
| 453 | m_defaultMaterial->setProperty(name: "blur" , value: 10.0); |
| 454 | } |
| 455 | |
| 456 | void 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 | |
| 482 | void QQuickRectangularShadowPrivate::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
| 483 | { |
| 484 | Q_UNUSED(value); |
| 485 | if (change == QQuickItem::ItemSceneChange) |
| 486 | initialize(); |
| 487 | } |
| 488 | |
| 489 | void QQuickRectangularShadowPrivate::handleGeometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
| 490 | { |
| 491 | Q_UNUSED(newGeometry); |
| 492 | Q_UNUSED(oldGeometry); |
| 493 | initialize(); |
| 494 | updateSizeProperties(); |
| 495 | } |
| 496 | |
| 497 | qreal QQuickRectangularShadowPrivate::getPadding() const |
| 498 | { |
| 499 | return qreal(m_blur * 2 + m_spread * 2); |
| 500 | } |
| 501 | |
| 502 | void QQuickRectangularShadowPrivate::updateColor() |
| 503 | { |
| 504 | currentMaterial()->setProperty(name: "color" , value: m_color); |
| 505 | } |
| 506 | |
| 507 | void 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 | |
| 521 | void 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(name: "iResolution" , value: 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(name: "rectSize" , value: rectSize); |
| 547 | material->setProperty(name: "radius" , value: 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(name: "blur" , value: shaderBlur); |
| 552 | } |
| 553 | |
| 554 | void QQuickRectangularShadowPrivate::updateCached() |
| 555 | { |
| 556 | QQuickItemPrivate *effectPrivate = QQuickItemPrivate::get(item: currentMaterial()); |
| 557 | effectPrivate->layer()->setEnabled(m_cached); |
| 558 | } |
| 559 | |
| 560 | qreal QQuickRectangularShadowPrivate::clampedRadius() const |
| 561 | { |
| 562 | Q_Q(const QQuickRectangularShadow); |
| 563 | qreal maxRadius = qMin(a: q->width(), b: q->height()) * 0.5; |
| 564 | maxRadius += m_spread * 2; |
| 565 | qreal spreadRadius = m_radius + m_spread; |
| 566 | if (m_radius < m_spread && !qFuzzyIsNull(d: 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(a: 0.0, b: spreadRadius - blurReduce); |
| 578 | return qMin(a: limitedRadius, b: maxRadius); |
| 579 | } |
| 580 | |
| 581 | QQuickItem *QQuickRectangularShadowPrivate::currentMaterial() const |
| 582 | { |
| 583 | if (m_material) |
| 584 | return m_material; |
| 585 | else |
| 586 | return m_defaultMaterial; |
| 587 | } |
| 588 | |
| 589 | QT_END_NAMESPACE |
| 590 | |