1// Copyright (C) 2016 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 "qquickshape_p.h"
5#include "qquickshape_p_p.h"
6#include "qquickshapegenericrenderer_p.h"
7#include "qquickshapesoftwarerenderer_p.h"
8#include "qquickshapecurverenderer_p.h"
9#include <private/qsgplaintexture_p.h>
10#include <private/qquicksvgparser_p.h>
11#include <QtGui/private/qdrawhelper_p.h>
12#include <QOpenGLFunctions>
13#include <QLoggingCategory>
14#include <rhi/qrhi.h>
15
16static void initResources()
17{
18#if defined(QT_STATIC)
19 Q_INIT_RESOURCE(qtquickshapes_shaders);
20#endif
21}
22
23QT_BEGIN_NAMESPACE
24
25Q_LOGGING_CATEGORY(QQSHAPE_LOG_TIME_DIRTY_SYNC, "qt.shape.time.sync")
26
27/*!
28 \qmlmodule QtQuick.Shapes 1.\QtMinorVersion
29 \title Qt Quick Shapes QML Types
30 \ingroup qmlmodules
31 \brief Provides QML types for drawing stroked and filled shapes.
32
33 To use the types in this module, import the module with the following line:
34
35 \qml
36 import QtQuick.Shapes
37 \endqml
38
39 Qt Quick Shapes provides tools for drawing arbitrary shapes in a Qt Quick scene.
40 \l{Shape}{Shapes} can be constructed from basic building blocks like \l{PathLine}{lines} and
41 \l{PathCubic}{curves} that define sub-shapes. The sub-shapes can then be filled with solid
42 colors or gradients, and an outline stroke can be defined.
43
44 Qt Quick Shapes also supports higher level path element types, such as \l{PathText}{text} and
45 \l{PathSvg}{SVG path descriptions}. The currently supported element types is: PathMove,
46 PathLine, PathQuad, PathCubic, PathArc, PathText and PathSvg.
47
48 Qt Quick Shapes triangulates the shapes and renders the corresponding triangles on the GPU.
49 Therefore, altering the control points of elements will lead to re-triangulation of the
50 affected paths, at some performance cost. In addition, curves are flattened before they are
51 rendered, so applying a very high scale to the shape may show artifacts where it is visible
52 that the curves are represented by a sequence of smaller, straight lines.
53
54 \note Qt Quick Shapes relies on multi-sampling for anti-aliasing. This can be enabled for the
55 entire application or window using the corresponding settings in QSurfaceFormat. It can also
56 be enabled for only the shape, by setting its \l{Item::layer.enabled}{layer.enabled} property to
57 true and then adjusting the \l{Item::layer.samples}{layer.samples} property. In the latter case,
58 multi-sampling will not be applied to the entire scene, but the shape will be rendered via an
59 intermediate off-screen buffer.
60
61 For further information, the \l{Qt Quick Examples - Shapes}{Shapes example} shows how to
62 implement different types of shapes, fills and strokes.
63*/
64
65void QQuickShapes_initializeModule()
66{
67 QQuickShapesModule::defineModule();
68}
69
70Q_CONSTRUCTOR_FUNCTION(QQuickShapes_initializeModule)
71
72void QQuickShapesModule::defineModule()
73{
74 initResources();
75}
76
77QQuickShapeStrokeFillParams::QQuickShapeStrokeFillParams()
78 : strokeColor(Qt::white),
79 strokeWidth(1),
80 fillColor(Qt::white),
81 fillRule(QQuickShapePath::OddEvenFill),
82 joinStyle(QQuickShapePath::BevelJoin),
83 miterLimit(2),
84 capStyle(QQuickShapePath::SquareCap),
85 strokeStyle(QQuickShapePath::SolidLine),
86 dashOffset(0),
87 fillGradient(nullptr)
88{
89 dashPattern << 4 << 2; // 4 * strokeWidth dash followed by 2 * strokeWidth space
90}
91
92/*!
93 \qmltype ShapePath
94 //! \instantiates QQuickShapePath
95 \inqmlmodule QtQuick.Shapes
96 \ingroup qtquick-paths
97 \ingroup qtquick-views
98 \inherits Path
99 \brief Describes a Path and associated properties for stroking and filling.
100 \since 5.10
101
102 A \l Shape contains one or more ShapePath elements. At least one ShapePath is
103 necessary in order to have a Shape output anything visible. A ShapePath
104 itself is a \l Path with additional properties describing the stroking and
105 filling parameters, such as the stroke width and color, the fill color or
106 gradient, join and cap styles, and so on. As with ordinary \l Path objects,
107 ShapePath also contains a list of path elements like \l PathMove, \l PathLine,
108 \l PathCubic, \l PathQuad, \l PathArc, together with a starting position.
109
110 Any property changes in these data sets will be bubble up and change the
111 output of the Shape. This means that it is simple and easy to change, or
112 even animate, the starting and ending position, control points, or any
113 stroke or fill parameters using the usual QML bindings and animation types
114 like NumberAnimation.
115
116 In the following example the line join style changes automatically based on
117 the value of joinStyleIndex:
118
119 \qml
120 ShapePath {
121 strokeColor: "black"
122 strokeWidth: 16
123 fillColor: "transparent"
124 capStyle: ShapePath.RoundCap
125
126 property int joinStyleIndex: 0
127
128 property variant styles: [
129 ShapePath.BevelJoin,
130 ShapePath.MiterJoin,
131 ShapePath.RoundJoin
132 ]
133
134 joinStyle: styles[joinStyleIndex]
135
136 startX: 30
137 startY: 30
138 PathLine { x: 100; y: 100 }
139 PathLine { x: 30; y: 100 }
140 }
141 \endqml
142
143 Once associated with a Shape, here is the output with a joinStyleIndex
144 of 2 (ShapePath.RoundJoin):
145
146 \image visualpath-code-example.png
147
148 \sa {Qt Quick Examples - Shapes}, Shape
149 */
150
151QQuickShapePathPrivate::QQuickShapePathPrivate()
152 : dirty(DirtyAll)
153{
154 // Set this QQuickPath to be a ShapePath
155 isShapePath = true;
156}
157
158QQuickShapePath::QQuickShapePath(QObject *parent)
159 : QQuickPath(*(new QQuickShapePathPrivate), parent)
160{
161 // The inherited changed() and the shapePathChanged() signals remain
162 // distinct, and this is intentional. Combining the two is not possible due
163 // to the difference in semantics and the need to act (see dirty flag
164 // below) differently on QQuickPath-related changes.
165
166 connect(sender: this, signal: &QQuickPath::changed, slot: [this]() {
167 Q_D(QQuickShapePath);
168 d->dirty |= QQuickShapePathPrivate::DirtyPath;
169 emit shapePathChanged();
170 });
171}
172
173QQuickShapePath::~QQuickShapePath()
174{
175}
176
177/*!
178 \qmlproperty color QtQuick.Shapes::ShapePath::strokeColor
179
180 This property holds the stroking color.
181
182 When set to \c transparent, no stroking occurs.
183
184 The default value is \c white.
185 */
186
187QColor QQuickShapePath::strokeColor() const
188{
189 Q_D(const QQuickShapePath);
190 return d->sfp.strokeColor;
191}
192
193void QQuickShapePath::setStrokeColor(const QColor &color)
194{
195 Q_D(QQuickShapePath);
196 if (d->sfp.strokeColor != color) {
197 d->sfp.strokeColor = color;
198 d->dirty |= QQuickShapePathPrivate::DirtyStrokeColor;
199 emit strokeColorChanged();
200 emit shapePathChanged();
201 }
202}
203
204/*!
205 \qmlproperty real QtQuick.Shapes::ShapePath::strokeWidth
206
207 This property holds the stroke width.
208
209 When set to a negative value, no stroking occurs.
210
211 The default value is 1.
212 */
213
214qreal QQuickShapePath::strokeWidth() const
215{
216 Q_D(const QQuickShapePath);
217 return d->sfp.strokeWidth;
218}
219
220void QQuickShapePath::setStrokeWidth(qreal w)
221{
222 Q_D(QQuickShapePath);
223 if (d->sfp.strokeWidth != w) {
224 d->sfp.strokeWidth = w;
225 d->dirty |= QQuickShapePathPrivate::DirtyStrokeWidth;
226 emit strokeWidthChanged();
227 emit shapePathChanged();
228 }
229}
230
231/*!
232 \qmlproperty color QtQuick.Shapes::ShapePath::fillColor
233
234 This property holds the fill color.
235
236 When set to \c transparent, no filling occurs.
237
238 The default value is \c white.
239 */
240
241QColor QQuickShapePath::fillColor() const
242{
243 Q_D(const QQuickShapePath);
244 return d->sfp.fillColor;
245}
246
247void QQuickShapePath::setFillColor(const QColor &color)
248{
249 Q_D(QQuickShapePath);
250 if (d->sfp.fillColor != color) {
251 d->sfp.fillColor = color;
252 d->dirty |= QQuickShapePathPrivate::DirtyFillColor;
253 emit fillColorChanged();
254 emit shapePathChanged();
255 }
256}
257
258/*!
259 \qmlproperty enumeration QtQuick.Shapes::ShapePath::fillRule
260
261 This property holds the fill rule. The default value is
262 \c ShapePath.OddEvenFill. For an explanation on fill rules, see
263 QPainterPath::setFillRule().
264
265 \value ShapePath.OddEvenFill
266 Odd-even fill rule.
267
268 \value ShapePath.WindingFill
269 Non-zero winding fill rule.
270 */
271
272QQuickShapePath::FillRule QQuickShapePath::fillRule() const
273{
274 Q_D(const QQuickShapePath);
275 return d->sfp.fillRule;
276}
277
278void QQuickShapePath::setFillRule(FillRule fillRule)
279{
280 Q_D(QQuickShapePath);
281 if (d->sfp.fillRule != fillRule) {
282 d->sfp.fillRule = fillRule;
283 d->dirty |= QQuickShapePathPrivate::DirtyFillRule;
284 emit fillRuleChanged();
285 emit shapePathChanged();
286 }
287}
288
289/*!
290 \qmlproperty enumeration QtQuick.Shapes::ShapePath::joinStyle
291
292 This property defines how joins between two connected lines are drawn. The
293 default value is \c ShapePath.BevelJoin.
294
295 \value ShapePath.MiterJoin
296 The outer edges of the lines are extended to meet at an angle, and
297 this area is filled.
298
299 \value ShapePath.BevelJoin
300 The triangular notch between the two lines is filled.
301
302 \value ShapePath.RoundJoin
303 A circular arc between the two lines is filled.
304 */
305
306QQuickShapePath::JoinStyle QQuickShapePath::joinStyle() const
307{
308 Q_D(const QQuickShapePath);
309 return d->sfp.joinStyle;
310}
311
312void QQuickShapePath::setJoinStyle(JoinStyle style)
313{
314 Q_D(QQuickShapePath);
315 if (d->sfp.joinStyle != style) {
316 d->sfp.joinStyle = style;
317 d->dirty |= QQuickShapePathPrivate::DirtyStyle;
318 emit joinStyleChanged();
319 emit shapePathChanged();
320 }
321}
322
323/*!
324 \qmlproperty int QtQuick.Shapes::ShapePath::miterLimit
325
326 When joinStyle is set to \c ShapePath.MiterJoin, this property
327 specifies how far the miter join can extend from the join point.
328
329 The default value is 2.
330 */
331
332int QQuickShapePath::miterLimit() const
333{
334 Q_D(const QQuickShapePath);
335 return d->sfp.miterLimit;
336}
337
338void QQuickShapePath::setMiterLimit(int limit)
339{
340 Q_D(QQuickShapePath);
341 if (d->sfp.miterLimit != limit) {
342 d->sfp.miterLimit = limit;
343 d->dirty |= QQuickShapePathPrivate::DirtyStyle;
344 emit miterLimitChanged();
345 emit shapePathChanged();
346 }
347}
348
349/*!
350 \qmlproperty enumeration QtQuick.Shapes::ShapePath::capStyle
351
352 This property defines how the end points of lines are drawn. The
353 default value is \c ShapePath.SquareCap.
354
355 \value ShapePath.FlatCap
356 A square line end that does not cover the end point of the line.
357
358 \value ShapePath.SquareCap
359 A square line end that covers the end point and extends beyond it
360 by half the line width.
361
362 \value ShapePath.RoundCap
363 A rounded line end.
364 */
365
366QQuickShapePath::CapStyle QQuickShapePath::capStyle() const
367{
368 Q_D(const QQuickShapePath);
369 return d->sfp.capStyle;
370}
371
372void QQuickShapePath::setCapStyle(CapStyle style)
373{
374 Q_D(QQuickShapePath);
375 if (d->sfp.capStyle != style) {
376 d->sfp.capStyle = style;
377 d->dirty |= QQuickShapePathPrivate::DirtyStyle;
378 emit capStyleChanged();
379 emit shapePathChanged();
380 }
381}
382
383/*!
384 \qmlproperty enumeration QtQuick.Shapes::ShapePath::strokeStyle
385
386 This property defines the style of stroking. The default value is
387 ShapePath.SolidLine.
388
389 \value ShapePath.SolidLine A plain line.
390 \value ShapePath.DashLine Dashes separated by a few pixels.
391 */
392
393QQuickShapePath::StrokeStyle QQuickShapePath::strokeStyle() const
394{
395 Q_D(const QQuickShapePath);
396 return d->sfp.strokeStyle;
397}
398
399void QQuickShapePath::setStrokeStyle(StrokeStyle style)
400{
401 Q_D(QQuickShapePath);
402 if (d->sfp.strokeStyle != style) {
403 d->sfp.strokeStyle = style;
404 d->dirty |= QQuickShapePathPrivate::DirtyDash;
405 emit strokeStyleChanged();
406 emit shapePathChanged();
407 }
408}
409
410/*!
411 \qmlproperty real QtQuick.Shapes::ShapePath::dashOffset
412
413 This property defines the starting point on the dash pattern, measured in
414 units used to specify the dash pattern.
415
416 The default value is 0.
417
418 \sa QPen::setDashOffset()
419 */
420
421qreal QQuickShapePath::dashOffset() const
422{
423 Q_D(const QQuickShapePath);
424 return d->sfp.dashOffset;
425}
426
427void QQuickShapePath::setDashOffset(qreal offset)
428{
429 Q_D(QQuickShapePath);
430 if (d->sfp.dashOffset != offset) {
431 d->sfp.dashOffset = offset;
432 d->dirty |= QQuickShapePathPrivate::DirtyDash;
433 emit dashOffsetChanged();
434 emit shapePathChanged();
435 }
436}
437
438/*!
439 \qmlproperty list<real> QtQuick.Shapes::ShapePath::dashPattern
440
441 This property defines the dash pattern when ShapePath.strokeStyle is set
442 to ShapePath.DashLine. The pattern must be specified as an even number of
443 positive entries where the entries 1, 3, 5... are the dashes and 2, 4,
444 6... are the spaces. The pattern is specified in units of the pen's width.
445
446 The default value is (4, 2), meaning a dash of 4 * ShapePath.strokeWidth
447 pixels followed by a space of 2 * ShapePath.strokeWidth pixels.
448
449 \sa QPen::setDashPattern()
450 */
451
452QVector<qreal> QQuickShapePath::dashPattern() const
453{
454 Q_D(const QQuickShapePath);
455 return d->sfp.dashPattern;
456}
457
458void QQuickShapePath::setDashPattern(const QVector<qreal> &array)
459{
460 Q_D(QQuickShapePath);
461 if (d->sfp.dashPattern != array) {
462 d->sfp.dashPattern = array;
463 d->dirty |= QQuickShapePathPrivate::DirtyDash;
464 emit dashPatternChanged();
465 emit shapePathChanged();
466 }
467}
468
469/*!
470 \qmlproperty ShapeGradient QtQuick.Shapes::ShapePath::fillGradient
471
472 This property defines the fill gradient. By default no gradient is enabled
473 and the value is \c null. In this case the fill uses a solid color based
474 on the value of ShapePath.fillColor.
475
476 When set, ShapePath.fillColor is ignored and filling is done using one of
477 the ShapeGradient subtypes.
478
479 \note The Gradient type cannot be used here. Rather, prefer using one of
480 the advanced subtypes, like LinearGradient.
481 */
482
483QQuickShapeGradient *QQuickShapePath::fillGradient() const
484{
485 Q_D(const QQuickShapePath);
486 return d->sfp.fillGradient;
487}
488
489void QQuickShapePath::setFillGradient(QQuickShapeGradient *gradient)
490{
491 Q_D(QQuickShapePath);
492 if (d->sfp.fillGradient != gradient) {
493 if (d->sfp.fillGradient)
494 qmlobject_disconnect(d->sfp.fillGradient, QQuickShapeGradient, SIGNAL(updated()),
495 this, QQuickShapePath, SLOT(_q_fillGradientChanged()));
496 d->sfp.fillGradient = gradient;
497 if (d->sfp.fillGradient)
498 qmlobject_connect(d->sfp.fillGradient, QQuickShapeGradient, SIGNAL(updated()),
499 this, QQuickShapePath, SLOT(_q_fillGradientChanged()));
500 d->dirty |= QQuickShapePathPrivate::DirtyFillGradient;
501 emit shapePathChanged();
502 }
503}
504
505void QQuickShapePathPrivate::_q_fillGradientChanged()
506{
507 Q_Q(QQuickShapePath);
508 dirty |= DirtyFillGradient;
509 emit q->shapePathChanged();
510}
511
512void QQuickShapePath::resetFillGradient()
513{
514 setFillGradient(nullptr);
515}
516
517/*!
518 \qmltype Shape
519 //! \instantiates QQuickShape
520 \inqmlmodule QtQuick.Shapes
521 \ingroup qtquick-paths
522 \ingroup qtquick-views
523 \inherits Item
524 \brief Renders a path.
525 \since 5.10
526
527 Renders a path by triangulating geometry from a QPainterPath.
528
529 This approach is different from rendering shapes via QQuickPaintedItem or
530 the 2D Canvas because the path never gets rasterized in software.
531 Therefore Shape is suitable for creating shapes spreading over larger
532 areas of the screen, avoiding the performance penalty for texture uploads
533 or framebuffer blits. In addition, the declarative API allows manipulating,
534 binding to, and even animating the path element properties like starting
535 and ending position, the control points, and so on.
536
537 The types for specifying path elements are shared between \l PathView and
538 Shape. However, not all Shape implementations support all path
539 element types, while some may not make sense for PathView. Shape's
540 currently supported subset is: PathMove, PathLine, PathQuad, PathCubic,
541 PathArc, PathText and PathSvg.
542
543 See \l Path for a detailed overview of the supported path elements.
544
545 \qml
546 Shape {
547 width: 200
548 height: 150
549 anchors.centerIn: parent
550 ShapePath {
551 strokeWidth: 4
552 strokeColor: "red"
553 fillGradient: LinearGradient {
554 x1: 20; y1: 20
555 x2: 180; y2: 130
556 GradientStop { position: 0; color: "blue" }
557 GradientStop { position: 0.2; color: "green" }
558 GradientStop { position: 0.4; color: "red" }
559 GradientStop { position: 0.6; color: "yellow" }
560 GradientStop { position: 1; color: "cyan" }
561 }
562 strokeStyle: ShapePath.DashLine
563 dashPattern: [ 1, 4 ]
564 startX: 20; startY: 20
565 PathLine { x: 180; y: 130 }
566 PathLine { x: 20; y: 130 }
567 PathLine { x: 20; y: 20 }
568 }
569 }
570 \endqml
571
572 \image pathitem-code-example.png
573
574 Like \l Item, Shape also allows any visual or non-visual objects to be
575 declared as children. ShapePath objects are handled specially. This is
576 useful since it allows adding visual items, like \l Rectangle or \l Image,
577 and non-visual objects, like \l Timer directly as children of Shape.
578
579 The following list summarizes the available Shape rendering approaches:
580
581 \list
582
583 \li When Qt Quick is running with the default, hardware-accelerated backend (RHI),
584 the generic shape renderer will be used. This converts the shapes into triangles
585 which are passed to the renderer.
586
587 \li The \c software backend is fully supported. The path is rendered via
588 QPainter::strokePath() and QPainter::fillPath() in this case.
589
590 \li The OpenVG backend is not currently supported.
591
592 \endlist
593
594 When using Shape, it is important to be aware of potential performance
595 implications:
596
597 \list
598
599 \li When the application is running with the generic, triangulation-based
600 Shape implementation, the geometry generation happens entirely on the
601 CPU. This is potentially expensive. Changing the set of path elements,
602 changing the properties of these elements, or changing certain properties
603 of the Shape itself all lead to retriangulation of the affected paths on
604 every change. Therefore, applying animation to such properties can affect
605 performance on less powerful systems.
606
607 \li However, the data-driven, declarative nature of the Shape API often
608 means better cacheability for the underlying CPU and GPU resources. A
609 property change in one ShapePath will only lead to reprocessing the
610 affected ShapePath, leaving other parts of the Shape unchanged. Therefore,
611 a frequently changing property can still result in a lower overall system
612 load than with imperative painting approaches (for example, QPainter).
613
614 \li At the same time, attention must be paid to the number of Shape
615 elements in the scene. The way such a Shape item is represented in
616 the scene graph is different from an ordinary geometry-based item,
617 and incurs a certain cost when it comes to OpenGL state changes.
618
619 \li As a general rule, scenes should avoid using separate Shape items when
620 it is not absolutely necessary. Prefer using one Shape item with multiple
621 ShapePath elements over multiple Shape items.
622
623 \endlist
624
625 \sa {Qt Quick Examples - Shapes}, Path, PathMove, PathLine, PathQuad, PathCubic, PathArc, PathSvg
626*/
627
628QQuickShapePrivate::QQuickShapePrivate()
629 : effectRefCount(0)
630{
631}
632
633QQuickShapePrivate::~QQuickShapePrivate()
634{
635 delete renderer;
636}
637
638void QQuickShapePrivate::_q_shapePathChanged()
639{
640 Q_Q(QQuickShape);
641 spChanged = true;
642 q->polish();
643 emit q->boundingRectChanged();
644}
645
646void QQuickShapePrivate::setStatus(QQuickShape::Status newStatus)
647{
648 Q_Q(QQuickShape);
649 if (status != newStatus) {
650 status = newStatus;
651 emit q->statusChanged();
652 }
653}
654
655QQuickShape::QQuickShape(QQuickItem *parent)
656 : QQuickItem(*(new QQuickShapePrivate), parent)
657{
658 setFlag(flag: ItemHasContents);
659}
660
661QQuickShape::~QQuickShape()
662{
663}
664
665/*!
666 \qmlproperty enumeration QtQuick.Shapes::Shape::rendererType
667
668 This property determines which path rendering backend is active.
669
670 \value Shape.UnknownRenderer
671 The renderer is unknown.
672
673 \value Shape.GeometryRenderer
674 The generic, driver independent solution for GPU rendering. Uses the same
675 CPU-based triangulation approach as QPainter's OpenGL 2 paint
676 engine. This is the default when the RHI-based Qt Quick scenegraph
677 backend is in use.
678
679 \value Shape.SoftwareRenderer
680 Pure QPainter drawing using the raster paint engine. This is the
681 default, and only, option when the Qt Quick scenegraph is running
682 with the \c software backend.
683
684 \value Shape.CurveRenderer
685 Experimental GPU-based renderer, added as technology preview in Qt 6.6.
686 In contrast to \c Shape.GeometryRenderer, curves are not approximated by short straight
687 lines. Instead, curves are rendered using a specialized fragment shader. This improves
688 visual quality and avoids re-tesselation performance hit when zooming. Also,
689 \c Shape.CurveRenderer provides native, high-quality anti-aliasing, without the
690 performance cost of multi- or supersampling.
691
692 By default, \c Shape.GeometryRenderer will be selected unless the Qt Quick scenegraph is running
693 with the \c software backend. In that case, \c Shape.SoftwareRenderer will be used.
694 \c Shape.CurveRenderer may be requested using the \l preferredRendererType property.
695
696 Note that \c Shape.CurveRenderer is currently regarded as experimental. The enum name of
697 this renderer may change in future versions of Qt, and some shapes may render incorrectly.
698 Among the known limitations are:
699 \list 1
700 \li Only quadratic curves are inherently supported. Cubic curves will be approximated by
701 quadratic curves.
702 \li Shapes where elements intersect are not rendered correctly. The \l [QML] {Path::simplify}
703 {Path.simplify} property may be used to remove self-intersections from such shapes, but
704 may incur a performance cost and reduced visual quality.
705 \li Shapes that span a large numerical range, such as a long string of text, may have
706 issues. Consider splitting these shapes into multiple ones, for instance by making
707 a \l PathText for each individual word.
708 \li If the shape is being rendered into a Qt Quick 3D scene, the
709 \c GL_OES_standard_derivatives extension to OpenGL is required when the OpenGL
710 RHI backend is in use (this is available by default on OpenGL ES 3 and later, but
711 optional in OpenGL ES 2).
712 \endlist
713*/
714
715QQuickShape::RendererType QQuickShape::rendererType() const
716{
717 Q_D(const QQuickShape);
718 return d->rendererType;
719}
720
721/*!
722 \qmlproperty enumeration QtQuick.Shapes::Shape::preferredRendererType
723 \since 6.6
724
725 Requests a specific backend to use for rendering the shape. The possible values are the same as
726 for \l rendererType. The default is \c Shape.UnknownRenderer, indicating no particular preference.
727
728 If the requested renderer type is not supported for the current Qt Quick backend, the default
729 renderer for that backend will be used instead. This will be reflected in the \l rendererType
730 when the backend is initialized.
731
732 \c Shape.SoftwareRenderer can currently not be selected without running the scenegraph with
733 the \c software backend, in which case it will be selected regardless of the
734 \c preferredRendererType.
735
736 \note This API is considered tech preview and may change or be removed in future versions of
737 Qt.
738
739 See \l rendererType for more information on the implications.
740*/
741
742QQuickShape::RendererType QQuickShape::preferredRendererType() const
743{
744 Q_D(const QQuickShape);
745 return d->preferredType;
746}
747
748void QQuickShape::setPreferredRendererType(QQuickShape::RendererType preferredType)
749{
750 Q_D(QQuickShape);
751 if (d->preferredType == preferredType)
752 return;
753
754 d->preferredType = preferredType;
755 // (could bail out here if selectRenderType shows no change?)
756
757 for (int i = 0; i < d->sp.size(); ++i) {
758 QQuickShapePath *p = d->sp[i];
759 QQuickShapePathPrivate *pp = QQuickShapePathPrivate::get(p);
760 pp->dirty |= QQuickShapePathPrivate::DirtyAll;
761 }
762 d->spChanged = true;
763 d->_q_shapePathChanged();
764 polish();
765 update();
766
767 emit preferredRendererTypeChanged();
768}
769
770
771/*!
772 \qmlproperty bool QtQuick.Shapes::Shape::asynchronous
773
774 When rendererType is \c Shape.GeometryRenderer, the input path is
775 triangulated on the CPU during the polishing phase of the Shape. This is
776 potentially expensive. To offload this work to separate worker threads,
777 set this property to \c true.
778
779 When enabled, making a Shape visible will not wait for the content to
780 become available. Instead, the GUI/main thread is not blocked and the
781 results of the path rendering are shown only when all the asynchronous
782 work has been finished.
783
784 The default value is \c false.
785 */
786
787bool QQuickShape::asynchronous() const
788{
789 Q_D(const QQuickShape);
790 return d->async;
791}
792
793void QQuickShape::setAsynchronous(bool async)
794{
795 Q_D(QQuickShape);
796 if (d->async != async) {
797 d->async = async;
798 emit asynchronousChanged();
799 if (d->componentComplete)
800 d->_q_shapePathChanged();
801 }
802}
803
804/*!
805 \qmlproperty rect QtQuick.Shapes::Shape::boundingRect
806 \since 6.6
807
808 Contains the united bounding rect of all sub paths in the shape.
809 */
810QRectF QQuickShape::boundingRect() const
811{
812 Q_D(const QQuickShape);
813 QRectF brect;
814 for (QQuickShapePath *path : d->sp) {
815 brect = brect.united(r: path->path().boundingRect());
816 }
817
818 return brect;
819}
820
821/*!
822 \qmlproperty bool QtQuick.Shapes::Shape::vendorExtensionsEnabled
823
824 This property controls the usage of non-standard OpenGL extensions.
825
826 The default value is \c false.
827
828 As of Qt 6.0 there are no vendor-specific rendering paths implemented.
829 */
830
831bool QQuickShape::vendorExtensionsEnabled() const
832{
833 Q_D(const QQuickShape);
834 return d->enableVendorExts;
835}
836
837void QQuickShape::setVendorExtensionsEnabled(bool enable)
838{
839 Q_D(QQuickShape);
840 if (d->enableVendorExts != enable) {
841 d->enableVendorExts = enable;
842 emit vendorExtensionsEnabledChanged();
843 }
844}
845
846/*!
847 \qmlproperty enumeration QtQuick.Shapes::Shape::status
848
849 This property determines the status of the Shape and is relevant when
850 Shape.asynchronous is set to \c true.
851
852 \value Shape.Null
853 Not yet initialized.
854
855 \value Shape.Ready
856 The Shape has finished processing.
857
858 \value Shape.Processing
859 The path is being processed.
860 */
861
862QQuickShape::Status QQuickShape::status() const
863{
864 Q_D(const QQuickShape);
865 return d->status;
866}
867
868/*!
869 \qmlproperty enumeration QtQuick.Shapes::Shape::containsMode
870 \since QtQuick.Shapes 1.11
871
872 This property determines the definition of \l {QQuickItem::contains()}{contains()}
873 for the Shape. It is useful in case you add \l {Qt Quick Input Handlers} and you want to
874 react only when the mouse or touchpoint is fully inside the Shape.
875
876 \value Shape.BoundingRectContains
877 The default implementation of \l QQuickItem::contains() checks only
878 whether the given point is inside the rectangular bounding box. This is
879 the most efficient implementation, which is why it's the default.
880
881 \value Shape.FillContains
882 Check whether the interior (the part that would be filled if you are
883 rendering it with fill) of any \l ShapePath that makes up this Shape
884 contains the given point. The more complex and numerous ShapePaths you
885 add, the less efficient this is to check, which can potentially slow
886 down event delivery in your application. So it should be used with care.
887
888 One way to speed up the \c FillContains check is to generate an approximate
889 outline with as few points as possible, place that in a transparent Shape
890 on top, and add your Pointer Handlers to that, so that the containment
891 check is cheaper during event delivery.
892*/
893QQuickShape::ContainsMode QQuickShape::containsMode() const
894{
895 Q_D(const QQuickShape);
896 return d->containsMode;
897}
898
899void QQuickShape::setContainsMode(QQuickShape::ContainsMode containsMode)
900{
901 Q_D(QQuickShape);
902 if (d->containsMode == containsMode)
903 return;
904
905 d->containsMode = containsMode;
906 emit containsModeChanged();
907}
908
909bool QQuickShape::contains(const QPointF &point) const
910{
911 Q_D(const QQuickShape);
912 switch (d->containsMode) {
913 case BoundingRectContains:
914 return QQuickItem::contains(point);
915 case FillContains:
916 for (QQuickShapePath *path : d->sp) {
917 if (path->path().contains(pt: point))
918 return true;
919 }
920 }
921 return false;
922}
923
924static void vpe_append(QQmlListProperty<QObject> *property, QObject *obj)
925{
926 QQuickShape *item = static_cast<QQuickShape *>(property->object);
927 QQuickShapePrivate *d = QQuickShapePrivate::get(item);
928 QQuickShapePath *path = qobject_cast<QQuickShapePath *>(object: obj);
929 if (path)
930 d->sp.append(t: path);
931
932 QQuickItemPrivate::data_append(property, obj);
933
934 if (path && d->componentComplete) {
935 QObject::connect(sender: path, SIGNAL(shapePathChanged()), receiver: item, SLOT(_q_shapePathChanged()));
936 d->_q_shapePathChanged();
937 }
938}
939
940static void vpe_clear(QQmlListProperty<QObject> *property)
941{
942 QQuickShape *item = static_cast<QQuickShape *>(property->object);
943 QQuickShapePrivate *d = QQuickShapePrivate::get(item);
944
945 for (QQuickShapePath *p : d->sp)
946 QObject::disconnect(sender: p, SIGNAL(shapePathChanged()), receiver: item, SLOT(_q_shapePathChanged()));
947
948 d->sp.clear();
949
950 QQuickItemPrivate::data_clear(property);
951
952 if (d->componentComplete)
953 d->_q_shapePathChanged();
954}
955
956/*!
957 \qmlproperty list<Object> QtQuick.Shapes::Shape::data
958
959 This property holds the ShapePath objects that define the contents of the
960 Shape. It can also contain any other type of objects, since Shape, like
961 Item, allows adding any visual or non-visual objects as children.
962
963 \qmldefault
964 */
965
966QQmlListProperty<QObject> QQuickShape::data()
967{
968 return QQmlListProperty<QObject>(this,
969 nullptr,
970 vpe_append,
971 QQuickItemPrivate::data_count,
972 QQuickItemPrivate::data_at,
973 vpe_clear);
974}
975
976void QQuickShape::classBegin()
977{
978 QQuickItem::classBegin();
979}
980
981void QQuickShape::componentComplete()
982{
983 Q_D(QQuickShape);
984
985 QQuickItem::componentComplete();
986
987 for (QQuickShapePath *p : d->sp)
988 connect(sender: p, SIGNAL(shapePathChanged()), receiver: this, SLOT(_q_shapePathChanged()));
989
990 d->_q_shapePathChanged();
991}
992
993void QQuickShape::updatePolish()
994{
995 Q_D(QQuickShape);
996
997 const int currentEffectRefCount = d->extra.isAllocated() ? d->extra->recursiveEffectRefCount : 0;
998 if (!d->spChanged && currentEffectRefCount <= d->effectRefCount)
999 return;
1000
1001 d->spChanged = false;
1002 d->effectRefCount = currentEffectRefCount;
1003
1004 QQuickShape::RendererType expectedRenderer = d->selectRendererType();
1005 if (d->rendererType != expectedRenderer) {
1006 delete d->renderer;
1007 d->renderer = nullptr;
1008 }
1009
1010 if (!d->renderer) {
1011 d->createRenderer();
1012 if (!d->renderer)
1013 return;
1014 emit rendererChanged();
1015 }
1016
1017 // endSync() is where expensive calculations may happen (or get kicked off
1018 // on worker threads), depending on the backend. Therefore do this only
1019 // when the item is visible.
1020 if (isVisible() || d->effectRefCount > 0)
1021 d->sync();
1022}
1023
1024void QQuickShape::itemChange(ItemChange change, const ItemChangeData &data)
1025{
1026 Q_D(QQuickShape);
1027
1028 // sync may have been deferred; do it now if the item became visible
1029 if (change == ItemVisibleHasChanged && data.boolValue)
1030 d->_q_shapePathChanged();
1031 else if (change == QQuickItem::ItemSceneChange) {
1032 for (int i = 0; i < d->sp.size(); ++i)
1033 QQuickShapePathPrivate::get(p: d->sp[i])->dirty = QQuickShapePathPrivate::DirtyAll;
1034 d->_q_shapePathChanged();
1035 }
1036
1037 QQuickItem::itemChange(change, data);
1038}
1039
1040QSGNode *QQuickShape::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
1041{
1042 // Called on the render thread, with the gui thread blocked. We can now
1043 // safely access gui thread data.
1044 Q_D(QQuickShape);
1045
1046 if (d->renderer || d->rendererChanged) {
1047 if (!node || d->rendererChanged) {
1048 d->rendererChanged = false;
1049 delete node;
1050 node = d->createNode();
1051 }
1052 if (d->renderer)
1053 d->renderer->updateNode();
1054 }
1055 return node;
1056}
1057
1058QQuickShape::RendererType QQuickShapePrivate::selectRendererType()
1059{
1060 QQuickShape::RendererType res = QQuickShape::UnknownRenderer;
1061 Q_Q(QQuickShape);
1062 QSGRendererInterface *ri = q->window()->rendererInterface();
1063 if (!ri)
1064 return res;
1065
1066 static const bool environmentPreferCurve =
1067 qEnvironmentVariable(varName: "QT_QUICKSHAPES_BACKEND").toLower() == QLatin1String("curverenderer");
1068
1069 switch (ri->graphicsApi()) {
1070 case QSGRendererInterface::Software:
1071 res = QQuickShape::SoftwareRenderer;
1072 break;
1073 default:
1074 if (QSGRendererInterface::isApiRhiBased(api: ri->graphicsApi())) {
1075 if (preferredType == QQuickShape::CurveRenderer || environmentPreferCurve) {
1076 res = QQuickShape::CurveRenderer;
1077 } else {
1078 res = QQuickShape::GeometryRenderer;
1079 }
1080 } else {
1081 qWarning(msg: "No path backend for this graphics API yet");
1082 }
1083 break;
1084 }
1085
1086 return res;
1087}
1088
1089// the renderer object lives on the gui thread
1090void QQuickShapePrivate::createRenderer()
1091{
1092 Q_Q(QQuickShape);
1093 QQuickShape::RendererType selectedType = selectRendererType();
1094 if (selectedType == QQuickShape::UnknownRenderer)
1095 return;
1096
1097 rendererType = selectedType;
1098 rendererChanged = true;
1099
1100 switch (selectedType) {
1101 case QQuickShape::SoftwareRenderer:
1102 renderer = new QQuickShapeSoftwareRenderer;
1103 break;
1104 case QQuickShape::GeometryRenderer:
1105 renderer = new QQuickShapeGenericRenderer(q);
1106 break;
1107 case QQuickShape::CurveRenderer:
1108 renderer = new QQuickShapeCurveRenderer(q);
1109 break;
1110 default:
1111 Q_UNREACHABLE();
1112 break;
1113 }
1114}
1115
1116// the node lives on the render thread
1117QSGNode *QQuickShapePrivate::createNode()
1118{
1119 Q_Q(QQuickShape);
1120 QSGNode *node = nullptr;
1121 if (!q->window() || !renderer)
1122 return node;
1123 QSGRendererInterface *ri = q->window()->rendererInterface();
1124 if (!ri)
1125 return node;
1126
1127 switch (ri->graphicsApi()) {
1128 case QSGRendererInterface::Software:
1129 node = new QQuickShapeSoftwareRenderNode(q);
1130 static_cast<QQuickShapeSoftwareRenderer *>(renderer)->setNode(
1131 static_cast<QQuickShapeSoftwareRenderNode *>(node));
1132 break;
1133 default:
1134 if (QSGRendererInterface::isApiRhiBased(api: ri->graphicsApi())) {
1135 if (rendererType == QQuickShape::CurveRenderer) {
1136 node = new QSGNode;
1137 static_cast<QQuickShapeCurveRenderer *>(renderer)->setRootNode(node);
1138 } else {
1139 node = new QQuickShapeGenericNode;
1140 static_cast<QQuickShapeGenericRenderer *>(renderer)->setRootNode(
1141 static_cast<QQuickShapeGenericNode *>(node));
1142 }
1143 } else {
1144 qWarning(msg: "No path backend for this graphics API yet");
1145 }
1146 break;
1147 }
1148
1149 return node;
1150}
1151
1152void QQuickShapePrivate::asyncShapeReady(void *data)
1153{
1154 QQuickShapePrivate *self = static_cast<QQuickShapePrivate *>(data);
1155 self->setStatus(QQuickShape::Ready);
1156 if (self->syncTimingActive)
1157 qDebug(msg: "[Shape %p] [%d] [dirty=0x%x] async update took %lld ms",
1158 self->q_func(), self->syncTimeCounter, self->syncTimingTotalDirty, self->syncTimer.elapsed());
1159}
1160
1161void QQuickShapePrivate::sync()
1162{
1163 int totalDirty = 0;
1164 syncTimingActive = QQSHAPE_LOG_TIME_DIRTY_SYNC().isDebugEnabled();
1165 if (syncTimingActive)
1166 syncTimer.start();
1167
1168 const bool useAsync = async && renderer->flags().testFlag(flag: QQuickAbstractPathRenderer::SupportsAsync);
1169 if (useAsync) {
1170 setStatus(QQuickShape::Processing);
1171 renderer->setAsyncCallback(asyncShapeReady, this);
1172 }
1173
1174 const int count = sp.size();
1175 bool countChanged = false;
1176 renderer->beginSync(totalCount: count, countChanged: &countChanged);
1177 renderer->setTriangulationScale(triangulationScale);
1178
1179 for (int i = 0; i < count; ++i) {
1180 QQuickShapePath *p = sp[i];
1181 int &dirty(QQuickShapePathPrivate::get(p)->dirty);
1182 totalDirty |= dirty;
1183
1184 if (dirty & QQuickShapePathPrivate::DirtyPath)
1185 renderer->setPath(index: i, path: p);
1186 if (dirty & QQuickShapePathPrivate::DirtyStrokeColor)
1187 renderer->setStrokeColor(index: i, color: p->strokeColor());
1188 if (dirty & QQuickShapePathPrivate::DirtyStrokeWidth)
1189 renderer->setStrokeWidth(index: i, w: p->strokeWidth());
1190 if (dirty & QQuickShapePathPrivate::DirtyFillColor)
1191 renderer->setFillColor(index: i, color: p->fillColor());
1192 if (dirty & QQuickShapePathPrivate::DirtyFillRule)
1193 renderer->setFillRule(index: i, fillRule: p->fillRule());
1194 if (dirty & QQuickShapePathPrivate::DirtyStyle) {
1195 renderer->setJoinStyle(index: i, joinStyle: p->joinStyle(), miterLimit: p->miterLimit());
1196 renderer->setCapStyle(index: i, capStyle: p->capStyle());
1197 }
1198 if (dirty & QQuickShapePathPrivate::DirtyDash)
1199 renderer->setStrokeStyle(index: i, strokeStyle: p->strokeStyle(), dashOffset: p->dashOffset(), dashPattern: p->dashPattern());
1200 if (dirty & QQuickShapePathPrivate::DirtyFillGradient)
1201 renderer->setFillGradient(index: i, gradient: p->fillGradient());
1202
1203 dirty = 0;
1204 }
1205
1206 syncTimingTotalDirty = totalDirty;
1207 if (syncTimingTotalDirty)
1208 ++syncTimeCounter;
1209 else
1210 syncTimingActive = false;
1211
1212 renderer->endSync(async: useAsync);
1213
1214 if (!useAsync) {
1215 setStatus(QQuickShape::Ready);
1216 if (syncTimingActive)
1217 qDebug(msg: "[Shape %p] [%d] [dirty=0x%x] update took %lld ms",
1218 q_func(), syncTimeCounter, syncTimingTotalDirty, syncTimer.elapsed());
1219 }
1220
1221 // Must dirty the QQuickItem if something got changed, nothing
1222 // else does this for us.
1223 Q_Q(QQuickShape);
1224 if (totalDirty || countChanged)
1225 q->update();
1226}
1227
1228// ***** gradient support *****
1229
1230/*!
1231 \qmltype ShapeGradient
1232 //! \instantiates QQuickShapeGradient
1233 \inqmlmodule QtQuick.Shapes
1234 \ingroup qtquick-paths
1235 \ingroup qtquick-views
1236 \inherits Gradient
1237 \brief Base type of Shape fill gradients.
1238 \since 5.10
1239
1240 This is an abstract base class for gradients like LinearGradient and
1241 cannot be created directly. It extends \l Gradient with properties like the
1242 spread mode.
1243 */
1244
1245QQuickShapeGradient::QQuickShapeGradient(QObject *parent)
1246 : QQuickGradient(parent),
1247 m_spread(PadSpread)
1248{
1249}
1250
1251/*!
1252 \qmlproperty enumeration QtQuick.Shapes::ShapeGradient::spread
1253
1254 Specifies how the area outside the gradient area should be filled. The
1255 default value is \c ShapeGradient.PadSpread.
1256
1257 \value ShapeGradient.PadSpread
1258 The area is filled with the closest stop color.
1259
1260 \value ShapeGradient.RepeatSpread
1261 The gradient is repeated outside the gradient area.
1262
1263 \value ShapeGradient.ReflectSpread
1264 The gradient is reflected outside the gradient area.
1265 */
1266
1267QQuickShapeGradient::SpreadMode QQuickShapeGradient::spread() const
1268{
1269 return m_spread;
1270}
1271
1272void QQuickShapeGradient::setSpread(SpreadMode mode)
1273{
1274 if (m_spread != mode) {
1275 m_spread = mode;
1276 emit spreadChanged();
1277 emit updated();
1278 }
1279}
1280
1281/*!
1282 \qmltype LinearGradient
1283 //! \instantiates QQuickShapeLinearGradient
1284 \inqmlmodule QtQuick.Shapes
1285 \ingroup qtquick-paths
1286 \ingroup qtquick-views
1287 \inherits ShapeGradient
1288 \brief Linear gradient.
1289 \since 5.10
1290
1291 Linear gradients interpolate colors between start and end points in Shape
1292 items. Outside these points the gradient is either padded, reflected or
1293 repeated depending on the spread type.
1294
1295 \note LinearGradient is only supported in combination with Shape items. It
1296 is not compatible with \l Rectangle, as that only supports \l Gradient.
1297
1298 \sa QLinearGradient
1299 */
1300
1301QQuickShapeLinearGradient::QQuickShapeLinearGradient(QObject *parent)
1302 : QQuickShapeGradient(parent)
1303{
1304}
1305
1306/*!
1307 \qmlproperty real QtQuick.Shapes::LinearGradient::x1
1308 \qmlproperty real QtQuick.Shapes::LinearGradient::y1
1309 \qmlproperty real QtQuick.Shapes::LinearGradient::x2
1310 \qmlproperty real QtQuick.Shapes::LinearGradient::y2
1311
1312 These properties define the start and end points between which color
1313 interpolation occurs. By default both points are set to (0, 0).
1314 */
1315
1316qreal QQuickShapeLinearGradient::x1() const
1317{
1318 return m_start.x();
1319}
1320
1321void QQuickShapeLinearGradient::setX1(qreal v)
1322{
1323 if (m_start.x() != v) {
1324 m_start.setX(v);
1325 emit x1Changed();
1326 emit updated();
1327 }
1328}
1329
1330qreal QQuickShapeLinearGradient::y1() const
1331{
1332 return m_start.y();
1333}
1334
1335void QQuickShapeLinearGradient::setY1(qreal v)
1336{
1337 if (m_start.y() != v) {
1338 m_start.setY(v);
1339 emit y1Changed();
1340 emit updated();
1341 }
1342}
1343
1344qreal QQuickShapeLinearGradient::x2() const
1345{
1346 return m_end.x();
1347}
1348
1349void QQuickShapeLinearGradient::setX2(qreal v)
1350{
1351 if (m_end.x() != v) {
1352 m_end.setX(v);
1353 emit x2Changed();
1354 emit updated();
1355 }
1356}
1357
1358qreal QQuickShapeLinearGradient::y2() const
1359{
1360 return m_end.y();
1361}
1362
1363void QQuickShapeLinearGradient::setY2(qreal v)
1364{
1365 if (m_end.y() != v) {
1366 m_end.setY(v);
1367 emit y2Changed();
1368 emit updated();
1369 }
1370}
1371
1372/*!
1373 \qmltype RadialGradient
1374 //! \instantiates QQuickShapeRadialGradient
1375 \inqmlmodule QtQuick.Shapes
1376 \ingroup qtquick-paths
1377 \ingroup qtquick-views
1378 \inherits ShapeGradient
1379 \brief Radial gradient.
1380 \since 5.10
1381
1382 Radial gradients interpolate colors between a focal circle and a center
1383 circle in Shape items. Points outside the cone defined by the two circles
1384 will be transparent.
1385
1386 Outside the end points the gradient is either padded, reflected or repeated
1387 depending on the spread type.
1388
1389 Below is an example of a simple radial gradient. Here the colors are
1390 interpolated between the specified point and the end points on a circle
1391 specified by the radius:
1392
1393 \code
1394 fillGradient: RadialGradient {
1395 centerX: 50; centerY: 50
1396 centerRadius: 100
1397 focalX: centerX; focalY: centerY
1398 GradientStop { position: 0; color: "blue" }
1399 GradientStop { position: 0.2; color: "green" }
1400 GradientStop { position: 0.4; color: "red" }
1401 GradientStop { position: 0.6; color: "yellow" }
1402 GradientStop { position: 1; color: "cyan" }
1403 }
1404 \endcode
1405
1406 \image shape-radial-gradient.png
1407
1408 Extended radial gradients, where a separate focal circle is specified, are
1409 also supported.
1410
1411 \note RadialGradient is only supported in combination with Shape items. It
1412 is not compatible with \l Rectangle, as that only supports \l Gradient.
1413
1414 \sa QRadialGradient
1415 */
1416
1417QQuickShapeRadialGradient::QQuickShapeRadialGradient(QObject *parent)
1418 : QQuickShapeGradient(parent)
1419{
1420}
1421
1422/*!
1423 \qmlproperty real QtQuick.Shapes::RadialGradient::centerX
1424 \qmlproperty real QtQuick.Shapes::RadialGradient::centerY
1425 \qmlproperty real QtQuick.Shapes::RadialGradient::focalX
1426 \qmlproperty real QtQuick.Shapes::RadialGradient::focalY
1427
1428 These properties define the center and focal points. To specify a simple
1429 radial gradient, set focalX and focalY to the value of centerX and
1430 centerY, respectively.
1431 */
1432
1433qreal QQuickShapeRadialGradient::centerX() const
1434{
1435 return m_centerPoint.x();
1436}
1437
1438void QQuickShapeRadialGradient::setCenterX(qreal v)
1439{
1440 if (m_centerPoint.x() != v) {
1441 m_centerPoint.setX(v);
1442 emit centerXChanged();
1443 emit updated();
1444 }
1445}
1446
1447qreal QQuickShapeRadialGradient::centerY() const
1448{
1449 return m_centerPoint.y();
1450}
1451
1452void QQuickShapeRadialGradient::setCenterY(qreal v)
1453{
1454 if (m_centerPoint.y() != v) {
1455 m_centerPoint.setY(v);
1456 emit centerYChanged();
1457 emit updated();
1458 }
1459}
1460
1461/*!
1462 \qmlproperty real QtQuick.Shapes::RadialGradient::centerRadius
1463 \qmlproperty real QtQuick.Shapes::RadialGradient::focalRadius
1464
1465 These properties define the center and focal radius. For simple radial
1466 gradients, focalRadius should be set to \c 0 (the default value).
1467 */
1468
1469qreal QQuickShapeRadialGradient::centerRadius() const
1470{
1471 return m_centerRadius;
1472}
1473
1474void QQuickShapeRadialGradient::setCenterRadius(qreal v)
1475{
1476 if (m_centerRadius != v) {
1477 m_centerRadius = v;
1478 emit centerRadiusChanged();
1479 emit updated();
1480 }
1481}
1482
1483qreal QQuickShapeRadialGradient::focalX() const
1484{
1485 return m_focalPoint.x();
1486}
1487
1488void QQuickShapeRadialGradient::setFocalX(qreal v)
1489{
1490 if (m_focalPoint.x() != v) {
1491 m_focalPoint.setX(v);
1492 emit focalXChanged();
1493 emit updated();
1494 }
1495}
1496
1497qreal QQuickShapeRadialGradient::focalY() const
1498{
1499 return m_focalPoint.y();
1500}
1501
1502void QQuickShapeRadialGradient::setFocalY(qreal v)
1503{
1504 if (m_focalPoint.y() != v) {
1505 m_focalPoint.setY(v);
1506 emit focalYChanged();
1507 emit updated();
1508 }
1509}
1510
1511qreal QQuickShapeRadialGradient::focalRadius() const
1512{
1513 return m_focalRadius;
1514}
1515
1516void QQuickShapeRadialGradient::setFocalRadius(qreal v)
1517{
1518 if (m_focalRadius != v) {
1519 m_focalRadius = v;
1520 emit focalRadiusChanged();
1521 emit updated();
1522 }
1523}
1524
1525/*!
1526 \qmltype ConicalGradient
1527 //! \instantiates QQuickShapeConicalGradient
1528 \inqmlmodule QtQuick.Shapes
1529 \ingroup qtquick-paths
1530 \ingroup qtquick-views
1531 \inherits ShapeGradient
1532 \brief Conical gradient.
1533 \since 5.10
1534
1535 Conical gradients interpolate colors counter-clockwise around a center
1536 point in Shape items.
1537
1538 \note The \l{ShapeGradient::spread}{spread mode} setting has no effect for
1539 conical gradients.
1540
1541 \note ConicalGradient is only supported in combination with Shape items. It
1542 is not compatible with \l Rectangle, as that only supports \l Gradient.
1543
1544 \sa QConicalGradient
1545 */
1546
1547QQuickShapeConicalGradient::QQuickShapeConicalGradient(QObject *parent)
1548 : QQuickShapeGradient(parent)
1549{
1550}
1551
1552/*!
1553 \qmlproperty real QtQuick.Shapes::ConicalGradient::centerX
1554 \qmlproperty real QtQuick.Shapes::ConicalGradient::centerY
1555
1556 These properties define the center point of the conical gradient.
1557 */
1558
1559qreal QQuickShapeConicalGradient::centerX() const
1560{
1561 return m_centerPoint.x();
1562}
1563
1564void QQuickShapeConicalGradient::setCenterX(qreal v)
1565{
1566 if (m_centerPoint.x() != v) {
1567 m_centerPoint.setX(v);
1568 emit centerXChanged();
1569 emit updated();
1570 }
1571}
1572
1573qreal QQuickShapeConicalGradient::centerY() const
1574{
1575 return m_centerPoint.y();
1576}
1577
1578void QQuickShapeConicalGradient::setCenterY(qreal v)
1579{
1580 if (m_centerPoint.y() != v) {
1581 m_centerPoint.setY(v);
1582 emit centerYChanged();
1583 emit updated();
1584 }
1585}
1586
1587/*!
1588 \qmlproperty real QtQuick.Shapes::ConicalGradient::angle
1589
1590 This property defines the start angle for the conical gradient. The value
1591 is in degrees (0-360).
1592 */
1593
1594qreal QQuickShapeConicalGradient::angle() const
1595{
1596 return m_angle;
1597}
1598
1599void QQuickShapeConicalGradient::setAngle(qreal v)
1600{
1601 if (m_angle != v) {
1602 m_angle = v;
1603 emit angleChanged();
1604 emit updated();
1605 }
1606}
1607
1608static void generateGradientColorTable(const QQuickShapeGradientCacheKey &gradient,
1609 uint *colorTable, int size, float opacity)
1610{
1611 int pos = 0;
1612 const QGradientStops &s = gradient.stops;
1613 Q_ASSERT(!s.isEmpty());
1614 const bool colorInterpolation = true;
1615
1616 uint alpha = qRound(f: opacity * 256);
1617 uint current_color = ARGB_COMBINE_ALPHA(s[0].second.rgba(), alpha);
1618 qreal incr = 1.0 / qreal(size);
1619 qreal fpos = 1.5 * incr;
1620 colorTable[pos++] = ARGB2RGBA(x: qPremultiply(x: current_color));
1621
1622 while (fpos <= s.first().first) {
1623 colorTable[pos] = colorTable[pos - 1];
1624 pos++;
1625 fpos += incr;
1626 }
1627
1628 if (colorInterpolation)
1629 current_color = qPremultiply(x: current_color);
1630
1631 const int sLast = s.size() - 1;
1632 for (int i = 0; i < sLast; ++i) {
1633 qreal delta = 1/(s[i+1].first - s[i].first);
1634 uint next_color = ARGB_COMBINE_ALPHA(s[i + 1].second.rgba(), alpha);
1635 if (colorInterpolation)
1636 next_color = qPremultiply(x: next_color);
1637
1638 while (fpos < s[i+1].first && pos < size) {
1639 int dist = int(256 * ((fpos - s[i].first) * delta));
1640 int idist = 256 - dist;
1641 if (colorInterpolation)
1642 colorTable[pos] = ARGB2RGBA(x: INTERPOLATE_PIXEL_256(x: current_color, a: idist, y: next_color, b: dist));
1643 else
1644 colorTable[pos] = ARGB2RGBA(x: qPremultiply(x: INTERPOLATE_PIXEL_256(x: current_color, a: idist, y: next_color, b: dist)));
1645 ++pos;
1646 fpos += incr;
1647 }
1648 current_color = next_color;
1649 }
1650
1651 uint last_color = ARGB2RGBA(x: qPremultiply(ARGB_COMBINE_ALPHA(s[sLast].second.rgba(), alpha)));
1652 for ( ; pos < size; ++pos)
1653 colorTable[pos] = last_color;
1654
1655 colorTable[size-1] = last_color;
1656}
1657
1658QQuickShapeGradientCache::~QQuickShapeGradientCache()
1659{
1660 qDeleteAll(c: m_textures);
1661}
1662
1663QQuickShapeGradientCache *QQuickShapeGradientCache::cacheForRhi(QRhi *rhi)
1664{
1665 static QHash<QRhi *, QQuickShapeGradientCache *> caches;
1666 auto it = caches.constFind(key: rhi);
1667 if (it != caches.constEnd())
1668 return *it;
1669
1670 QQuickShapeGradientCache *cache = new QQuickShapeGradientCache;
1671 rhi->addCleanupCallback(callback: [cache](QRhi *rhi) {
1672 caches.remove(key: rhi);
1673 delete cache;
1674 });
1675 caches.insert(key: rhi, value: cache);
1676 return cache;
1677}
1678
1679QSGTexture *QQuickShapeGradientCache::get(const QQuickShapeGradientCacheKey &grad)
1680{
1681 QSGPlainTexture *tx = m_textures[grad];
1682 if (!tx) {
1683 static const int W = 1024; // texture size is 1024x1
1684 QImage gradTab(W, 1, QImage::Format_RGBA8888_Premultiplied);
1685 if (!grad.stops.isEmpty())
1686 generateGradientColorTable(gradient: grad, colorTable: reinterpret_cast<uint *>(gradTab.bits()), size: W, opacity: 1.0f);
1687 else
1688 gradTab.fill(color: Qt::black);
1689 tx = new QSGPlainTexture;
1690 tx->setImage(gradTab);
1691 switch (grad.spread) {
1692 case QQuickShapeGradient::PadSpread:
1693 tx->setHorizontalWrapMode(QSGTexture::ClampToEdge);
1694 tx->setVerticalWrapMode(QSGTexture::ClampToEdge);
1695 break;
1696 case QQuickShapeGradient::RepeatSpread:
1697 tx->setHorizontalWrapMode(QSGTexture::Repeat);
1698 tx->setVerticalWrapMode(QSGTexture::Repeat);
1699 break;
1700 case QQuickShapeGradient::ReflectSpread:
1701 tx->setHorizontalWrapMode(QSGTexture::MirroredRepeat);
1702 tx->setVerticalWrapMode(QSGTexture::MirroredRepeat);
1703 break;
1704 default:
1705 qWarning(msg: "Unknown gradient spread mode %d", grad.spread);
1706 break;
1707 }
1708 tx->setFiltering(QSGTexture::Linear);
1709 m_textures[grad] = tx;
1710 }
1711 return tx;
1712}
1713
1714QT_END_NAMESPACE
1715
1716#include "moc_qquickshape_p.cpp"
1717

source code of qtdeclarative/src/quickshapes/qquickshape.cpp