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_STATIC_LOGGING_CATEGORY(QQSHAPE_LOG_TIME_DIRTY_SYNC, "qt.shape.time.sync")
26Q_STATIC_LOGGING_CATEGORY(lcShapeSync, "qt.quick.shapes.shape.sync")
27
28/*!
29 \keyword Qt Quick Shapes
30 \qmlmodule QtQuick.Shapes 1.\QtMinorVersion
31 \title Qt Quick Shapes QML Types
32 \ingroup qmlmodules
33 \brief Provides QML types for drawing stroked and filled shapes.
34
35 To use the types in this module, import the module with the following line:
36
37 \qml
38 import QtQuick.Shapes
39 \endqml
40
41 Qt Quick Shapes provides tools for drawing arbitrary shapes in a Qt Quick scene.
42 \l{Shape}{Shapes} can be constructed from basic building blocks like \l{PathLine}{lines} and
43 \l{PathCubic}{curves} that define sub-shapes. The sub-shapes can then be filled with solid
44 colors or gradients, and an outline stroke can be defined.
45
46 Qt Quick Shapes also supports higher level path element types, such as \l{PathText}{text} and
47 \l{PathSvg}{SVG path descriptions}. The currently supported element types is: PathMove,
48 PathLine, PathQuad, PathCubic, PathArc, PathText and PathSvg.
49
50 Qt Quick Shapes triangulates the shapes and renders the corresponding triangles on the GPU.
51 Therefore, altering the control points of elements will lead to re-triangulation of the
52 affected paths, at some performance cost. In addition, curves are flattened before they are
53 rendered, so applying a very high scale to the shape may show artifacts where it is visible
54 that the curves are represented by a sequence of smaller, straight lines.
55
56 \note By default, Qt Quick Shapes relies on multi-sampling for anti-aliasing. This can be
57 enabled for the entire application or window using the corresponding settings in QSurfaceFormat.
58 It can also be enabled for only the shape, by setting its \l{Item::layer.enabled}{layer.enabled}
59 property to true and then adjusting the \l{Item::layer.samples}{layer.samples} property. In the
60 latter case, multi-sampling will not be applied to the entire scene, but the shape will be
61 rendered via an intermediate off-screen buffer. Alternatively, the
62 \l{QtQuick.Shapes::Shape::preferredRendererType}{preferredRendererType} property can be set
63 to \c{Shape.CurveRenderer}. This has anti-aliasing built in and generally renders the shapes
64 at a higher quality, but at some additional performance cost.
65
66 For further information, the \l{Qt Quick Examples - Shapes}{Shapes example} shows how to
67 implement different types of shapes, fills and strokes, and the \l{Weather Forecast Example}
68 shows examples of different ways shapes might be useful in a user interface.
69*/
70
71void QQuickShapes_initializeModule()
72{
73 QQuickShapesModule::defineModule();
74}
75
76Q_CONSTRUCTOR_FUNCTION(QQuickShapes_initializeModule)
77
78void QQuickShapesModule::defineModule()
79{
80 initResources();
81}
82
83QQuickShapeStrokeFillParams::QQuickShapeStrokeFillParams()
84 : strokeColor(Qt::white),
85 strokeWidth(1),
86 fillColor(Qt::white),
87 fillRule(QQuickShapePath::OddEvenFill),
88 joinStyle(QQuickShapePath::BevelJoin),
89 miterLimit(2),
90 capStyle(QQuickShapePath::SquareCap),
91 strokeStyle(QQuickShapePath::SolidLine),
92 dashOffset(0),
93 fillGradient(nullptr),
94 fillItem(nullptr),
95 trim(nullptr)
96{
97 dashPattern << 4 << 2; // 4 * strokeWidth dash followed by 2 * strokeWidth space
98}
99
100/*!
101 \qmltype ShapePath
102 //! \nativetype QQuickShapePath
103 \inqmlmodule QtQuick.Shapes
104 \ingroup qtquick-paths
105 \ingroup qtquick-views
106 \inherits Path
107 \brief Describes a Path and associated properties for stroking and filling.
108 \since 5.10
109
110 A \l Shape contains one or more ShapePath elements. At least one ShapePath is
111 necessary in order to have a Shape output anything visible. A ShapePath
112 itself is a \l Path with additional properties describing the stroking and
113 filling parameters, such as the stroke width and color, the fill color or
114 gradient, join and cap styles, and so on. As with ordinary \l Path objects,
115 ShapePath also contains a list of path elements like \l PathMove, \l PathLine,
116 \l PathCubic, \l PathQuad, \l PathArc, together with a starting position.
117
118 Any property changes in these data sets will be bubble up and change the
119 output of the Shape. This means that it is simple and easy to change, or
120 even animate, the starting and ending position, control points, or any
121 stroke or fill parameters using the usual QML bindings and animation types
122 like NumberAnimation.
123
124 In the following example the line join style changes automatically based on
125 the value of joinStyleIndex:
126
127 \qml
128 ShapePath {
129 strokeColor: "black"
130 strokeWidth: 16
131 fillColor: "transparent"
132 capStyle: ShapePath.RoundCap
133
134 property int joinStyleIndex: 0
135
136 property variant styles: [
137 ShapePath.BevelJoin,
138 ShapePath.MiterJoin,
139 ShapePath.RoundJoin
140 ]
141
142 joinStyle: styles[joinStyleIndex]
143
144 startX: 30
145 startY: 30
146 PathLine { x: 100; y: 100 }
147 PathLine { x: 30; y: 100 }
148 }
149 \endqml
150
151 Once associated with a Shape, here is the output with a joinStyleIndex
152 of 2 (ShapePath.RoundJoin):
153
154 \image visualpath-code-example.png
155
156 \sa {Qt Quick Examples - Shapes}, {Weather Forecast Example}, Shape
157 */
158
159QQuickShapePathPrivate::QQuickShapePathPrivate()
160 : dirty(DirtyAll)
161{
162 // Set this QQuickPath to be a ShapePath
163 isShapePath = true;
164}
165
166QQuickShapePath::QQuickShapePath(QObject *parent)
167 : QQuickPath(*(new QQuickShapePathPrivate), parent)
168{
169 // The inherited changed() and the shapePathChanged() signals remain
170 // distinct, and this is intentional. Combining the two is not possible due
171 // to the difference in semantics and the need to act (see dirty flag
172 // below) differently on QQuickPath-related changes.
173
174 connect(sender: this, signal: &QQuickPath::changed, slot: [this]() {
175 Q_D(QQuickShapePath);
176 d->dirty |= QQuickShapePathPrivate::DirtyPath;
177 emit shapePathChanged();
178 });
179}
180
181QQuickShapePath::~QQuickShapePath()
182{
183}
184
185/*!
186 \qmlproperty color QtQuick.Shapes::ShapePath::strokeColor
187
188 This property holds the stroking color.
189
190 When set to \c transparent, no stroking occurs.
191
192 The default value is \c white.
193 */
194
195QColor QQuickShapePath::strokeColor() const
196{
197 Q_D(const QQuickShapePath);
198 return d->sfp.strokeColor;
199}
200
201void QQuickShapePath::setStrokeColor(const QColor &color)
202{
203 Q_D(QQuickShapePath);
204 if (d->sfp.strokeColor != color) {
205 d->sfp.strokeColor = color;
206 d->dirty |= QQuickShapePathPrivate::DirtyStrokeColor;
207 emit strokeColorChanged();
208 emit shapePathChanged();
209 }
210}
211
212/*!
213 \qmlproperty real QtQuick.Shapes::ShapePath::strokeWidth
214
215 This property holds the stroke width.
216
217 When set to a negative value, no stroking occurs.
218
219 The default value is 1.
220 */
221
222qreal QQuickShapePath::strokeWidth() const
223{
224 Q_D(const QQuickShapePath);
225 return d->sfp.strokeWidth;
226}
227
228void QQuickShapePath::setStrokeWidth(qreal w)
229{
230 Q_D(QQuickShapePath);
231 if (d->sfp.strokeWidth != w) {
232 d->sfp.strokeWidth = w;
233 d->dirty |= QQuickShapePathPrivate::DirtyStrokeWidth;
234 emit strokeWidthChanged();
235 emit shapePathChanged();
236 }
237}
238
239/*!
240 \qmlproperty color QtQuick.Shapes::ShapePath::fillColor
241
242 This property holds the fill color.
243
244 When set to \c transparent, no filling occurs.
245
246 The default value is \c white.
247
248 \note If either \l fillGradient or \l fillItem are set to something other than \c null, these
249 will take precedence over \c fillColor. The \c fillColor will be ignored in this case.
250 */
251
252QColor QQuickShapePath::fillColor() const
253{
254 Q_D(const QQuickShapePath);
255 return d->sfp.fillColor;
256}
257
258void QQuickShapePath::setFillColor(const QColor &color)
259{
260 Q_D(QQuickShapePath);
261 if (d->sfp.fillColor != color) {
262 d->sfp.fillColor = color;
263 d->dirty |= QQuickShapePathPrivate::DirtyFillColor;
264 emit fillColorChanged();
265 emit shapePathChanged();
266 }
267}
268
269/*!
270 \include shapepath.qdocinc {fillRule-property} {QtQuick.Shapes::ShapePath}
271*/
272
273QQuickShapePath::FillRule QQuickShapePath::fillRule() const
274{
275 Q_D(const QQuickShapePath);
276 return d->sfp.fillRule;
277}
278
279void QQuickShapePath::setFillRule(FillRule fillRule)
280{
281 Q_D(QQuickShapePath);
282 if (d->sfp.fillRule != fillRule) {
283 d->sfp.fillRule = fillRule;
284 d->dirty |= QQuickShapePathPrivate::DirtyFillRule;
285 emit fillRuleChanged();
286 emit shapePathChanged();
287 }
288}
289
290/*!
291 \include shapepath.qdocinc {joinStyle-property} {QtQuick.Shapes::ShapePath}
292*/
293
294QQuickShapePath::JoinStyle QQuickShapePath::joinStyle() const
295{
296 Q_D(const QQuickShapePath);
297 return d->sfp.joinStyle;
298}
299
300void QQuickShapePath::setJoinStyle(JoinStyle style)
301{
302 Q_D(QQuickShapePath);
303 if (d->sfp.joinStyle != style) {
304 d->sfp.joinStyle = style;
305 d->dirty |= QQuickShapePathPrivate::DirtyStyle;
306 emit joinStyleChanged();
307 emit shapePathChanged();
308 }
309}
310
311/*!
312 \qmlproperty int QtQuick.Shapes::ShapePath::miterLimit
313
314 When joinStyle is set to \c ShapePath.MiterJoin, this property
315 specifies how far the miter join can extend from the join point.
316
317 The default value is 2.
318 */
319
320int QQuickShapePath::miterLimit() const
321{
322 Q_D(const QQuickShapePath);
323 return d->sfp.miterLimit;
324}
325
326void QQuickShapePath::setMiterLimit(int limit)
327{
328 Q_D(QQuickShapePath);
329 if (d->sfp.miterLimit != limit) {
330 d->sfp.miterLimit = limit;
331 d->dirty |= QQuickShapePathPrivate::DirtyStyle;
332 emit miterLimitChanged();
333 emit shapePathChanged();
334 }
335}
336
337/*!
338 \include shapepath.qdocinc {capStyle-property} {QtQuick.Shapes::ShapePath}
339*/
340
341QQuickShapePath::CapStyle QQuickShapePath::capStyle() const
342{
343 Q_D(const QQuickShapePath);
344 return d->sfp.capStyle;
345}
346
347void QQuickShapePath::setCapStyle(CapStyle style)
348{
349 Q_D(QQuickShapePath);
350 if (d->sfp.capStyle != style) {
351 d->sfp.capStyle = style;
352 d->dirty |= QQuickShapePathPrivate::DirtyStyle;
353 emit capStyleChanged();
354 emit shapePathChanged();
355 }
356}
357
358/*!
359 \include shapepath.qdocinc {strokeStyle-property} {QtQuick.Shapes::ShapePath}
360*/
361
362QQuickShapePath::StrokeStyle QQuickShapePath::strokeStyle() const
363{
364 Q_D(const QQuickShapePath);
365 return d->sfp.strokeStyle;
366}
367
368void QQuickShapePath::setStrokeStyle(StrokeStyle style)
369{
370 Q_D(QQuickShapePath);
371 if (d->sfp.strokeStyle != style) {
372 d->sfp.strokeStyle = style;
373 d->dirty |= QQuickShapePathPrivate::DirtyDash;
374 emit strokeStyleChanged();
375 emit shapePathChanged();
376 }
377}
378
379/*!
380 \include shapepath.qdocinc {dashOffset-property} {QtQuick.Shapes::ShapePath}
381*/
382
383qreal QQuickShapePath::dashOffset() const
384{
385 Q_D(const QQuickShapePath);
386 return d->sfp.dashOffset;
387}
388
389void QQuickShapePath::setDashOffset(qreal offset)
390{
391 Q_D(QQuickShapePath);
392 if (d->sfp.dashOffset != offset) {
393 d->sfp.dashOffset = offset;
394 d->dirty |= QQuickShapePathPrivate::DirtyDash;
395 emit dashOffsetChanged();
396 emit shapePathChanged();
397 }
398}
399
400/*!
401 \include shapepath.qdocinc {dashPattern-property} {QtQuick.Shapes::ShapePath}
402*/
403
404QVector<qreal> QQuickShapePath::dashPattern() const
405{
406 Q_D(const QQuickShapePath);
407 return d->sfp.dashPattern;
408}
409
410void QQuickShapePath::setDashPattern(const QVector<qreal> &array)
411{
412 Q_D(QQuickShapePath);
413 if (d->sfp.dashPattern != array) {
414 d->sfp.dashPattern = array;
415 d->dirty |= QQuickShapePathPrivate::DirtyDash;
416 emit dashPatternChanged();
417 emit shapePathChanged();
418 }
419}
420
421/*!
422 \qmlproperty ShapeGradient QtQuick.Shapes::ShapePath::fillGradient
423
424 This property defines the fill gradient. By default no gradient is enabled
425 and the value is \c null. In this case the fill will either be based on the \l fillItem
426 property if it is set, and otherwise the \l{fillColor} property will be used.
427
428 \note The Gradient type cannot be used here. Rather, prefer using one of
429 the advanced subtypes, like LinearGradient.
430
431 \note If set to something other than \c{null}, the \c fillGradient will take precedence over
432 both \l fillItem and \l fillColor.
433 */
434
435QQuickShapeGradient *QQuickShapePath::fillGradient() const
436{
437 Q_D(const QQuickShapePath);
438 return d->sfp.fillGradient;
439}
440
441void QQuickShapePath::setFillGradient(QQuickShapeGradient *gradient)
442{
443 Q_D(QQuickShapePath);
444 if (d->sfp.fillGradient != gradient) {
445 if (d->sfp.fillGradient)
446 qmlobject_disconnect(d->sfp.fillGradient, QQuickShapeGradient, SIGNAL(updated()),
447 this, QQuickShapePath, SLOT(_q_fillGradientChanged()));
448 d->sfp.fillGradient = gradient;
449 if (d->sfp.fillGradient)
450 qmlobject_connect(d->sfp.fillGradient, QQuickShapeGradient, SIGNAL(updated()),
451 this, QQuickShapePath, SLOT(_q_fillGradientChanged()));
452 d->dirty |= QQuickShapePathPrivate::DirtyFillGradient;
453 emit shapePathChanged();
454 }
455}
456
457void QQuickShapePath::resetFillGradient()
458{
459 setFillGradient(nullptr);
460}
461
462void QQuickShapePathPrivate::_q_fillGradientChanged()
463{
464 Q_Q(QQuickShapePath);
465 dirty |= DirtyFillGradient;
466 emit q->shapePathChanged();
467}
468
469/*!
470 \qmlproperty Item QtQuick.Shapes::ShapePath::fillItem
471 \since 6.8
472
473 This property defines another Qt Quick Item to use as fill by the shape. The item must be
474 texture provider (such as a \l {Item Layers} {layered item}, a \l{ShaderEffectSource} or an
475 \l{Image}). If it is not a valid texture provider, this property will be ignored.
476
477 The visual parent of \c fillItem must be a Qt Quick \l{Item}. In particular, since \c{ShapePath}
478 is not an \l{Item}, its children cannot be used as fill items. Manually setting the
479 \c{fillItem}'s parent is needed when it is created as a child of the \c{ShapePath}.
480
481 For instance, creating an \l{Image} object directly in the \c{fillItem} property assignment will
482 make it a child of the \c{ShapePath}. In this case, its parent must be set manually. In the
483 following example we use the window's \l{Window::contentItem}{contentItem} as the parent.
484
485 \code
486 fillItem: Image {
487 visible: false
488 source: "contents.png"
489 parent: window.contentItem
490 }
491 \endcode
492
493 \note When using a layered item as a \c fillItem, you may see pixelation effects when
494 transforming the fill. Setting the \l {QtQuick::Item::}{layer.smooth} property to true will
495 give better visual results in this case.
496
497 By default no fill item is set and the value is \c null.
498
499 \note If set to something other than \c null, the \c fillItem property takes precedence over
500 \l fillColor. The \l fillGradient property in turn takes precedence over both \c fillItem and
501 \l{fillColor}.
502 */
503
504QQuickItem *QQuickShapePath::fillItem() const
505{
506 Q_D(const QQuickShapePath);
507 return d->sfp.fillItem;
508}
509
510void QQuickShapePath::setFillItem(QQuickItem *fillItem)
511{
512 Q_D(QQuickShapePath);
513 if (d->sfp.fillItem != fillItem) {
514 if (d->sfp.fillItem != nullptr) {
515 qmlobject_disconnect(d->sfp.fillItem, QQuickItem, SIGNAL(destroyed()),
516 this, QQuickShapePath, SLOT(_q_fillItemDestroyed()));
517 }
518 d->sfp.fillItem = fillItem;
519 if (d->sfp.fillItem != nullptr) {
520 qmlobject_connect(d->sfp.fillItem, QQuickItem, SIGNAL(destroyed()),
521 this, QQuickShapePath, SLOT(_q_fillItemDestroyed()));
522 }
523 emit fillItemChanged();
524
525 d->dirty |= QQuickShapePathPrivate::DirtyFillItem;
526 emit shapePathChanged();
527 }
528}
529
530/*!
531 \qmlpropertygroup QtQuick.Shapes::ShapePath::trim
532 \qmlproperty real QtQuick.Shapes::ShapePath::trim.start
533 \qmlproperty real QtQuick.Shapes::ShapePath::trim.stop
534 \qmlproperty real QtQuick.Shapes::ShapePath::trim.offset
535 \since 6.10
536
537 Specifies the section of this path that will be displayed.
538
539 The section is defined by the path length fractions \c start and \c stop. By default, \c start
540 is 0 (denoting the start of the path) and \c stop is 1 (denoting the end of the path), so the
541 entire path is displayed.
542
543 The value of \c offset is added to \c start and \c stop. If that causes over- or underrun of the
544 [0, 1] range, the values will be wrapped around, as will the resulting path section. The
545 effective range of \c offset is between -1 and 1. The default value is 0.
546*/
547
548QQuickShapeTrim *QQuickShapePath::trim()
549{
550 Q_D(QQuickShapePath);
551 if (!d->sfp.trim) {
552 d->sfp.trim = new QQuickShapeTrim;
553 QQml_setParent_noEvent(object: d->sfp.trim, parent: this);
554 }
555 return d->sfp.trim;
556}
557
558bool QQuickShapePath::hasTrim() const
559{
560 Q_D(const QQuickShapePath);
561 return d->sfp.trim != nullptr;
562}
563
564void QQuickShapePathPrivate::_q_fillItemDestroyed()
565{
566 Q_Q(QQuickShapePath);
567 sfp.fillItem = nullptr;
568 dirty |= DirtyFillItem;
569 emit q->fillItemChanged();
570 emit q->shapePathChanged();
571}
572
573#ifndef QT_NO_DEBUG_STREAM
574void QQuickShapePathPrivate::writeToDebugStream(QDebug &debug) const
575{
576 debug.nospace() << "QQuickShapePath(" << (const void *)this
577 << " startX=" << startX
578 << " startY=" << startY
579 << " _pathElements=" << _pathElements
580 << ')';
581}
582#endif
583
584/*!
585 \qmlproperty PathHints QtQuick.Shapes::ShapePath::pathHints
586 \since 6.7
587
588 This property describes characteristics of the shape. If set, these hints may allow
589 optimized rendering. By default, no hints are set. It can be a combination of the following
590 values:
591
592 \value ShapePath.PathLinear
593 The path only has straight lines, no curves.
594 \value ShapePath.PathQuadratic
595 The path does not have any cubic curves: only lines and quadratic Bezier curves.
596 \value ShapePath.PathConvex
597 The path does not have any dents or holes. All straight lines between two points
598 inside the shape will be completely inside the shape.
599 \value ShapePath.PathFillOnRight
600 The path follows the TrueType convention where outlines around solid fill have their
601 control points ordered clockwise, and outlines around holes in the shape have their
602 control points ordered counter-clockwise.
603 \value ShapePath.PathSolid
604 The path has no holes, or mathematically speaking it is \e{simply connected}.
605 \value ShapePath.PathNonIntersecting
606 The path outline does not cross itself.
607 \value ShapePath.PathNonOverlappingControlPointTriangles
608 The triangles defined by the curve control points do not overlap with each other,
609 or with any of the line segments. Also, no line segments intersect.
610 This implies \c PathNonIntersecting.
611
612 Not all hints are logically independent, but the dependencies are not enforced.
613 For example, \c PathLinear implies \c PathQuadratic, but it is valid to have \c PathLinear
614 without \c PathQuadratic.
615
616 The pathHints property describes a set of statements known to be true; the absence of a hint
617 does not necessarily mean that the corresponding statement is false.
618*/
619
620QQuickShapePath::PathHints QQuickShapePath::pathHints() const
621{
622 Q_D(const QQuickShapePath);
623 return d->pathHints;
624}
625
626void QQuickShapePath::setPathHints(PathHints newPathHints)
627{
628 Q_D(QQuickShapePath);
629 if (d->pathHints == newPathHints)
630 return;
631 d->pathHints = newPathHints;
632 emit pathHintsChanged();
633}
634
635/*!
636 \qmlproperty matrix4x4 QtQuick.Shapes::ShapePath::fillTransform
637 \since 6.8
638
639 This property defines a transform to be applied to the path's fill pattern (\l fillGradient or
640 \l fillItem). It has no effect if the fill is a solid color or transparent. By default no fill
641 transform is enabled and the value of this property is the \c identity matrix.
642
643 This example displays a rectangle filled with the contents of \c myImageItem rotated 45 degrees
644 around the center point of \c myShape:
645
646 \qml
647 ShapePath {
648 fillItem: myImageItem
649 fillTransform: PlanarTransform.fromRotate(45, myShape.width / 2, myShape.height / 2)
650 PathRectangle { x: 10; y: 10; width: myShape.width - 20; height: myShape.height - 20 }
651 }
652 \endqml
653*/
654
655QMatrix4x4 QQuickShapePath::fillTransform() const
656{
657 Q_D(const QQuickShapePath);
658 return d->sfp.fillTransform.matrix();
659}
660
661void QQuickShapePath::setFillTransform(const QMatrix4x4 &matrix)
662{
663 Q_D(QQuickShapePath);
664 if (d->sfp.fillTransform != matrix) {
665 d->sfp.fillTransform.setMatrix(matrix);
666 d->dirty |= QQuickShapePathPrivate::DirtyFillTransform;
667 emit fillTransformChanged();
668 emit shapePathChanged();
669 }
670}
671
672
673QQuickShapeTrim::QQuickShapeTrim(QObject *parent)
674 : QObject(parent)
675{
676}
677
678qreal QQuickShapeTrim::start() const
679{
680 return m_start;
681}
682
683void QQuickShapeTrim::setStart(qreal t)
684{
685 if (t == m_start)
686 return;
687 m_start = t;
688 QQuickShapePath *shapePath = qobject_cast<QQuickShapePath *>(object: parent());
689 if (shapePath) {
690 QQuickShapePathPrivate *d = QQuickShapePathPrivate::get(p: shapePath);
691 d->dirty |= QQuickShapePathPrivate::DirtyTrim;
692 emit startChanged();
693 emit shapePath->shapePathChanged();
694 }
695}
696
697qreal QQuickShapeTrim::end() const
698{
699 return m_end;
700}
701
702void QQuickShapeTrim::setEnd(qreal t)
703{
704 if (t == m_end)
705 return;
706 m_end = t;
707 QQuickShapePath *shapePath = qobject_cast<QQuickShapePath *>(object: parent());
708 if (shapePath) {
709 QQuickShapePathPrivate *d = QQuickShapePathPrivate::get(p: shapePath);
710 d->dirty |= QQuickShapePathPrivate::DirtyTrim;
711 emit endChanged();
712 emit shapePath->shapePathChanged();
713 }
714}
715
716qreal QQuickShapeTrim::offset() const
717{
718 return m_offset;
719}
720
721void QQuickShapeTrim::setOffset(qreal t)
722{
723 if (t == m_offset)
724 return;
725 m_offset = t;
726 QQuickShapePath *shapePath = qobject_cast<QQuickShapePath *>(object: parent());
727 if (shapePath) {
728 QQuickShapePathPrivate *d = QQuickShapePathPrivate::get(p: shapePath);
729 d->dirty |= QQuickShapePathPrivate::DirtyTrim;
730 emit offsetChanged();
731 emit shapePath->shapePathChanged();
732 }
733}
734
735/*!
736 \qmltype Shape
737 //! \nativetype QQuickShape
738 \inqmlmodule QtQuick.Shapes
739 \ingroup qtquick-paths
740 \ingroup qtquick-views
741 \inherits Item
742 \brief Renders a path.
743 \since 5.10
744
745 Renders a path by triangulating geometry from a QPainterPath.
746
747 This approach is different from rendering shapes via QQuickPaintedItem or
748 the 2D Canvas because the path never gets rasterized in software.
749 Therefore Shape is suitable for creating shapes spreading over larger
750 areas of the screen, avoiding the performance penalty for texture uploads
751 or framebuffer blits. In addition, the declarative API allows manipulating,
752 binding to, and even animating the path element properties like starting
753 and ending position, the control points, and so on.
754
755 The types for specifying path elements are shared between \l PathView and
756 Shape. However, not all Shape implementations support all path
757 element types, while some may not make sense for PathView. Shape's
758 currently supported subset is: PathMove, PathLine, PathQuad, PathCubic,
759 PathArc, PathText and PathSvg.
760
761 See \l Path for a detailed overview of the supported path elements.
762
763 \qml
764 Shape {
765 width: 200
766 height: 150
767 anchors.centerIn: parent
768 ShapePath {
769 strokeWidth: 4
770 strokeColor: "red"
771 fillGradient: LinearGradient {
772 x1: 20; y1: 20
773 x2: 180; y2: 130
774 GradientStop { position: 0; color: "blue" }
775 GradientStop { position: 0.2; color: "green" }
776 GradientStop { position: 0.4; color: "red" }
777 GradientStop { position: 0.6; color: "yellow" }
778 GradientStop { position: 1; color: "cyan" }
779 }
780 strokeStyle: ShapePath.DashLine
781 dashPattern: [ 1, 4 ]
782 startX: 20; startY: 20
783 PathLine { x: 180; y: 130 }
784 PathLine { x: 20; y: 130 }
785 PathLine { x: 20; y: 20 }
786 }
787 }
788 \endqml
789
790 \image pathitem-code-example.png
791
792 Like \l Item, Shape also allows any visual or non-visual objects to be
793 declared as children. ShapePath objects are handled specially. This is
794 useful since it allows adding visual items, like \l Rectangle or \l Image,
795 and non-visual objects, like \l Timer directly as children of Shape.
796
797 The following list summarizes the available Shape rendering approaches:
798
799 \list
800
801 \li When Qt Quick is running with the default, hardware-accelerated backend (RHI),
802 the generic shape renderer will be used. This converts the shapes into triangles
803 which are passed to the renderer.
804
805 \li The \c software backend is fully supported. The path is rendered via
806 QPainter::strokePath() and QPainter::fillPath() in this case.
807
808 \li The OpenVG backend is not currently supported.
809
810 \endlist
811
812 When using Shape, it is important to be aware of potential performance
813 implications:
814
815 \list
816
817 \li When the application is running with the generic, triangulation-based
818 Shape implementation, the geometry generation happens entirely on the
819 CPU. This is potentially expensive. Changing the set of path elements,
820 changing the properties of these elements, or changing certain properties
821 of the Shape itself all lead to retriangulation of the affected paths on
822 every change. Therefore, applying animation to such properties can affect
823 performance on less powerful systems.
824
825 \li However, the data-driven, declarative nature of the Shape API often
826 means better cacheability for the underlying CPU and GPU resources. A
827 property change in one ShapePath will only lead to reprocessing the
828 affected ShapePath, leaving other parts of the Shape unchanged. Therefore,
829 a frequently changing property can still result in a lower overall system
830 load than with imperative painting approaches (for example, QPainter).
831
832 \li At the same time, attention must be paid to the number of Shape
833 elements in the scene. The way such a Shape item is represented in
834 the scene graph is different from an ordinary geometry-based item,
835 and incurs a certain cost when it comes to OpenGL state changes.
836
837 \li As a general rule, scenes should avoid using separate Shape items when
838 it is not absolutely necessary. Prefer using one Shape item with multiple
839 ShapePath elements over multiple Shape items.
840
841 \endlist
842
843 \sa {Qt Quick Examples - Shapes}, {Weather Forecast Example}, Path, PathMove, PathLine, PathQuad, PathCubic, PathArc, PathSvg
844*/
845
846QQuickShapePrivate::QQuickShapePrivate()
847 : effectRefCount(0)
848{
849}
850
851QQuickShapePrivate::~QQuickShapePrivate()
852{
853 delete renderer;
854}
855
856void QQuickShapePrivate::init()
857{
858 Q_Q(QQuickShape);
859 q->setFlag(flag: QQuickItem::ItemHasContents);
860}
861
862void QQuickShapePrivate::_q_shapePathChanged()
863{
864 Q_Q(QQuickShape);
865 spChanged = true;
866 q->polish();
867 emit q->boundingRectChanged();
868 auto br = q->boundingRect();
869 q->setImplicitSize(br.right(), br.bottom());
870}
871
872void QQuickShapePrivate::handleSceneChange(QQuickWindow *w)
873{
874 if (renderer != nullptr)
875 renderer->handleSceneChange(window: w);
876}
877
878void QQuickShapePrivate::setStatus(QQuickShape::Status newStatus)
879{
880 Q_Q(QQuickShape);
881 if (status != newStatus) {
882 status = newStatus;
883 emit q->statusChanged();
884 }
885}
886
887qreal QQuickShapePrivate::getImplicitWidth() const
888{
889 Q_Q(const QQuickShape);
890 return q->boundingRect().right();
891}
892
893qreal QQuickShapePrivate::getImplicitHeight() const
894{
895 Q_Q(const QQuickShape);
896 return q->boundingRect().bottom();
897}
898
899QQuickShape::QQuickShape(QQuickItem *parent)
900 : QQuickItem(*(new QQuickShapePrivate), parent)
901{
902 Q_D(QQuickShape);
903 d->init();
904}
905
906QQuickShape::QQuickShape(QQuickShapePrivate &dd, QQuickItem *parent)
907 : QQuickItem(dd, parent)
908{
909 Q_D(QQuickShape);
910 d->init();
911}
912
913QQuickShape::~QQuickShape()
914{
915}
916
917/*!
918 \qmlproperty enumeration QtQuick.Shapes::Shape::rendererType
919 \readonly
920
921 This property determines which path rendering backend is active.
922
923 \value Shape.UnknownRenderer
924 The renderer is unknown.
925
926 \value Shape.GeometryRenderer
927 The generic, driver independent solution for GPU rendering. Uses the same
928 CPU-based triangulation approach as QPainter's OpenGL 2 paint
929 engine. This is the default when the RHI-based Qt Quick scenegraph
930 backend is in use.
931
932 \value Shape.SoftwareRenderer
933 Pure QPainter drawing using the raster paint engine. This is the
934 default, and only, option when the Qt Quick scenegraph is running
935 with the \c software backend.
936
937 \value Shape.CurveRenderer
938 GPU-based renderer that aims to preserve curvature at any scale.
939 In contrast to \c Shape.GeometryRenderer, curves are not approximated by short straight
940 lines. Instead, curves are rendered using a specialized fragment shader. This improves
941 visual quality and avoids re-tesselation performance hit when zooming. Also,
942 \c Shape.CurveRenderer provides native, high-quality anti-aliasing, without the
943 performance cost of multi- or supersampling.
944
945 By default, \c Shape.GeometryRenderer will be selected unless the Qt Quick scenegraph is running
946 with the \c software backend. In that case, \c Shape.SoftwareRenderer will be used.
947 \c Shape.CurveRenderer may be requested using the \l preferredRendererType property.
948
949 \note The \c Shape.CurveRenderer will approximate cubic curves with quadratic ones and may
950 therefore diverge slightly from the mathematically correct visualization of the shape. In
951 addition, if the shape is being rendered into a Qt Quick 3D scene and the OpenGL backend for
952 RHI is active, the \c GL_OES_standard_derivatives extension to OpenGL is required (this is
953 available by default on OpenGL ES 3 and later, but optional in OpenGL ES 2.)
954*/
955
956QQuickShape::RendererType QQuickShape::rendererType() const
957{
958 Q_D(const QQuickShape);
959 return d->rendererType;
960}
961
962/*!
963 \qmlproperty enumeration QtQuick.Shapes::Shape::preferredRendererType
964 \since 6.6
965
966 Requests a specific backend to use for rendering the shape. The possible values are the same as
967 for \l rendererType. The default is \c Shape.UnknownRenderer, indicating no particular preference.
968
969 If the requested renderer type is not supported for the current Qt Quick backend, the default
970 renderer for that backend will be used instead. This will be reflected in the \l rendererType
971 when the backend is initialized.
972
973 \c Shape.SoftwareRenderer can currently not be selected without running the scenegraph with
974 the \c software backend, in which case it will be selected regardless of the
975 \c preferredRendererType.
976
977 See \l rendererType for more information on the implications.
978*/
979
980QQuickShape::RendererType QQuickShape::preferredRendererType() const
981{
982 Q_D(const QQuickShape);
983 return d->preferredType;
984}
985
986void QQuickShape::setPreferredRendererType(QQuickShape::RendererType preferredType)
987{
988 Q_D(QQuickShape);
989 if (d->preferredType == preferredType)
990 return;
991
992 d->preferredType = preferredType;
993 // (could bail out here if selectRenderType shows no change?)
994
995 for (int i = 0; i < d->sp.size(); ++i) {
996 QQuickShapePath *p = d->sp[i];
997 QQuickShapePathPrivate *pp = QQuickShapePathPrivate::get(p);
998 pp->dirty |= QQuickShapePathPrivate::DirtyAll;
999 }
1000 d->spChanged = true;
1001 d->_q_shapePathChanged();
1002 polish();
1003 update();
1004
1005 emit preferredRendererTypeChanged();
1006}
1007
1008/*!
1009 \qmlproperty bool QtQuick.Shapes::Shape::asynchronous
1010
1011 When rendererType is \c Shape.GeometryRenderer or \c Shape.CurveRenderer, a certain amount of
1012 preprocessing of the input path is performed on the CPU during the polishing phase of the
1013 Shape. This is potentially expensive. To offload this work to separate worker threads, set this
1014 property to \c true.
1015
1016 When enabled, making a Shape visible will not wait for the content to
1017 become available. Instead, the GUI/main thread is not blocked and the
1018 results of the path rendering are shown only when all the asynchronous
1019 work has been finished.
1020
1021 The default value is \c false.
1022 */
1023
1024bool QQuickShape::asynchronous() const
1025{
1026 Q_D(const QQuickShape);
1027 return d->async;
1028}
1029
1030void QQuickShape::setAsynchronous(bool async)
1031{
1032 Q_D(QQuickShape);
1033 if (d->async != async) {
1034 d->async = async;
1035 emit asynchronousChanged();
1036 if (d->componentComplete)
1037 d->_q_shapePathChanged();
1038 }
1039}
1040
1041/*!
1042 \qmlproperty rect QtQuick.Shapes::Shape::boundingRect
1043 \readonly
1044 \since 6.6
1045
1046 Contains the united bounding rect of all sub paths in the shape.
1047 */
1048QRectF QQuickShape::boundingRect() const
1049{
1050 Q_D(const QQuickShape);
1051 QRectF brect;
1052 for (QQuickShapePath *path : d->sp) {
1053 qreal pw = path->strokeColor().alpha() ? path->strokeWidth() : 0;
1054 qreal d = path->capStyle() == QQuickShapePath::SquareCap ? pw * M_SQRT1_2 : pw / 2;
1055 brect = brect.united(r: path->path().boundingRect().adjusted(xp1: -d, yp1: -d, xp2: d, yp2: d));
1056 }
1057
1058 return brect;
1059}
1060
1061/*!
1062 \qmlproperty bool QtQuick.Shapes::Shape::vendorExtensionsEnabled
1063
1064 This property controls the usage of non-standard OpenGL extensions.
1065
1066 The default value is \c false.
1067
1068 As of Qt 6.0 there are no vendor-specific rendering paths implemented.
1069 */
1070
1071bool QQuickShape::vendorExtensionsEnabled() const
1072{
1073 Q_D(const QQuickShape);
1074 return d->enableVendorExts;
1075}
1076
1077void QQuickShape::setVendorExtensionsEnabled(bool enable)
1078{
1079 Q_D(QQuickShape);
1080 if (d->enableVendorExts != enable) {
1081 d->enableVendorExts = enable;
1082 emit vendorExtensionsEnabledChanged();
1083 }
1084}
1085
1086/*!
1087 \qmlproperty enumeration QtQuick.Shapes::Shape::status
1088 \readonly
1089
1090 This property determines the status of the Shape and is relevant when
1091 Shape.asynchronous is set to \c true.
1092
1093 \value Shape.Null
1094 Not yet initialized.
1095
1096 \value Shape.Ready
1097 The Shape has finished processing.
1098
1099 \value Shape.Processing
1100 The path is being processed.
1101 */
1102
1103QQuickShape::Status QQuickShape::status() const
1104{
1105 Q_D(const QQuickShape);
1106 return d->status;
1107}
1108
1109/*!
1110 \qmlproperty enumeration QtQuick.Shapes::Shape::containsMode
1111 \since QtQuick.Shapes 1.11
1112
1113 This property determines the definition of \l {QQuickItem::contains()}{contains()}
1114 for the Shape. It is useful in case you add \l {Qt Quick Input Handlers} and you want to
1115 react only when the mouse or touchpoint is fully inside the Shape.
1116
1117 \value Shape.BoundingRectContains
1118 The default implementation of \l QQuickItem::contains() checks only
1119 whether the given point is inside the rectangular bounding box. This is
1120 the most efficient implementation, which is why it's the default.
1121
1122 \value Shape.FillContains
1123 Check whether the interior (the part that would be filled if you are
1124 rendering it with fill) of any \l ShapePath that makes up this Shape
1125 contains the given point. The more complex and numerous ShapePaths you
1126 add, the less efficient this is to check, which can potentially slow
1127 down event delivery in your application. So it should be used with care.
1128
1129 One way to speed up the \c FillContains check is to generate an approximate
1130 outline with as few points as possible, place that in a transparent Shape
1131 on top, and add your Pointer Handlers to that, so that the containment
1132 check is cheaper during event delivery.
1133*/
1134QQuickShape::ContainsMode QQuickShape::containsMode() const
1135{
1136 Q_D(const QQuickShape);
1137 return d->containsMode;
1138}
1139
1140void QQuickShape::setContainsMode(QQuickShape::ContainsMode containsMode)
1141{
1142 Q_D(QQuickShape);
1143 if (d->containsMode == containsMode)
1144 return;
1145
1146 d->containsMode = containsMode;
1147 emit containsModeChanged();
1148}
1149
1150bool QQuickShape::contains(const QPointF &point) const
1151{
1152 Q_D(const QQuickShape);
1153 switch (d->containsMode) {
1154 case BoundingRectContains:
1155 return QQuickItem::contains(point);
1156 case FillContains:
1157 for (QQuickShapePath *path : d->sp) {
1158 if (path->path().contains(pt: point))
1159 return true;
1160 }
1161 }
1162 return false;
1163}
1164
1165/*!
1166 \qmlproperty enumeration QtQuick.Shapes::Shape::fillMode
1167 \since QtQuick.Shapes 6.7
1168
1169 Set this property to define what happens when the path has a different size
1170 than the item.
1171
1172 \value Shape.NoResize the shape is rendered at its native size, independent of the size of the item. This is the default
1173 \value Shape.Stretch the shape is scaled to fit the item, changing the aspect ratio if necessary.
1174 Note that non-uniform scaling may cause reduced quality of anti-aliasing when using the curve renderer
1175 \value Shape.PreserveAspectFit the shape is scaled uniformly to fit inside the item
1176 \value Shape.PreserveAspectCrop the shape is scaled uniformly to fill the item fully, extending outside the item if necessary.
1177 Note that this only actually crops the content if \l clip is true
1178*/
1179
1180QQuickShape::FillMode QQuickShape::fillMode() const
1181{
1182 Q_D(const QQuickShape);
1183 return d->fillMode;
1184}
1185
1186void QQuickShape::setFillMode(FillMode newFillMode)
1187{
1188 Q_D(QQuickShape);
1189 if (d->fillMode == newFillMode)
1190 return;
1191 d->fillMode = newFillMode;
1192 emit fillModeChanged();
1193}
1194
1195/*!
1196 \qmlproperty enumeration QtQuick.Shapes::Shape::horizontalAlignment
1197 \qmlproperty enumeration QtQuick.Shapes::Shape::verticalAlignment
1198 \since 6.7
1199
1200 Sets the horizontal and vertical alignment of the shape within the item.
1201 By default, the shape is aligned with \c{(0,0)} on the top left corner.
1202
1203 The valid values for \c horizontalAlignment are \c Shape.AlignLeft,
1204 \c Shape.AlignRight and \c Shape.AlignHCenter. The valid values for
1205 \c verticalAlignment are \c Shape.AlignTop, \c Shape.AlignBottom and
1206 \c Shape.AlignVCenter.
1207*/
1208
1209QQuickShape::HAlignment QQuickShape::horizontalAlignment() const
1210{
1211 Q_D(const QQuickShape);
1212 return d->horizontalAlignment;
1213}
1214
1215void QQuickShape::setHorizontalAlignment(HAlignment newHorizontalAlignment)
1216{
1217 Q_D(QQuickShape);
1218 if (d->horizontalAlignment == newHorizontalAlignment)
1219 return;
1220 d->horizontalAlignment = newHorizontalAlignment;
1221 emit horizontalAlignmentChanged();
1222}
1223
1224QQuickShape::VAlignment QQuickShape::verticalAlignment() const
1225{
1226 Q_D(const QQuickShape);
1227 return d->verticalAlignment;
1228}
1229
1230void QQuickShape::setVerticalAlignment(VAlignment newVerticalAlignment)
1231{
1232 Q_D(QQuickShape);
1233 if (d->verticalAlignment == newVerticalAlignment)
1234 return;
1235 d->verticalAlignment = newVerticalAlignment;
1236 emit verticalAlignmentChanged();
1237}
1238
1239static void vpe_append(QQmlListProperty<QObject> *property, QObject *obj)
1240{
1241 QQuickShape *item = static_cast<QQuickShape *>(property->object);
1242 QQuickShapePrivate *d = QQuickShapePrivate::get(item);
1243 QQuickShapePath *path = qobject_cast<QQuickShapePath *>(object: obj);
1244 if (path) {
1245 QQuickShapePathPrivate::get(p: path)->dirty = QQuickShapePathPrivate::DirtyAll;
1246 d->sp.append(t: path);
1247 }
1248
1249 QQuickItemPrivate::data_append(property, obj);
1250
1251 if (path && d->componentComplete) {
1252 QObject::connect(sender: path, SIGNAL(shapePathChanged()), receiver: item, SLOT(_q_shapePathChanged()));
1253 d->_q_shapePathChanged();
1254 }
1255}
1256
1257static void vpe_clear(QQmlListProperty<QObject> *property)
1258{
1259 QQuickShape *item = static_cast<QQuickShape *>(property->object);
1260 QQuickShapePrivate *d = QQuickShapePrivate::get(item);
1261
1262 for (QQuickShapePath *p : d->sp)
1263 QObject::disconnect(sender: p, SIGNAL(shapePathChanged()), receiver: item, SLOT(_q_shapePathChanged()));
1264
1265 d->sp.clear();
1266
1267 QQuickItemPrivate::data_clear(property);
1268
1269 if (d->componentComplete)
1270 d->_q_shapePathChanged();
1271}
1272
1273/*!
1274 \qmlproperty list<Object> QtQuick.Shapes::Shape::data
1275
1276 This property holds the ShapePath objects that define the contents of the
1277 Shape. It can also contain any other type of objects, since Shape, like
1278 Item, allows adding any visual or non-visual objects as children.
1279
1280 \qmldefault
1281 */
1282
1283QQmlListProperty<QObject> QQuickShape::data()
1284{
1285 return QQmlListProperty<QObject>(this,
1286 nullptr,
1287 vpe_append,
1288 QQuickItemPrivate::data_count,
1289 QQuickItemPrivate::data_at,
1290 vpe_clear);
1291}
1292
1293void QQuickShape::classBegin()
1294{
1295 QQuickItem::classBegin();
1296}
1297
1298void QQuickShape::componentComplete()
1299{
1300 Q_D(QQuickShape);
1301
1302 QQuickItem::componentComplete();
1303
1304 for (QQuickShapePath *p : d->sp)
1305 connect(sender: p, SIGNAL(shapePathChanged()), receiver: this, SLOT(_q_shapePathChanged()));
1306
1307 d->_q_shapePathChanged();
1308}
1309
1310void QQuickShape::updatePolish()
1311{
1312 Q_D(QQuickShape);
1313
1314 const int currentEffectRefCount = d->extra.isAllocated() ? d->extra->recursiveEffectRefCount : 0;
1315 if (!d->spChanged && currentEffectRefCount <= d->effectRefCount)
1316 return;
1317
1318 d->spChanged = false;
1319 d->effectRefCount = currentEffectRefCount;
1320
1321 QQuickShape::RendererType expectedRenderer = d->selectRendererType();
1322 if (d->rendererType != expectedRenderer) {
1323 delete d->renderer;
1324 d->renderer = nullptr;
1325 }
1326
1327 if (!d->renderer) {
1328 d->createRenderer();
1329 if (!d->renderer)
1330 return;
1331 emit rendererChanged();
1332 }
1333
1334 // endSync() is where expensive calculations may happen (or get kicked off
1335 // on worker threads), depending on the backend. Therefore do this only
1336 // when the item is visible.
1337 if (isVisible() || d->effectRefCount > 0)
1338 d->sync();
1339}
1340
1341void QQuickShape::itemChange(ItemChange change, const ItemChangeData &data)
1342{
1343 Q_D(QQuickShape);
1344
1345 // sync may have been deferred; do it now if the item became visible
1346 if (change == ItemVisibleHasChanged && data.boolValue)
1347 d->_q_shapePathChanged();
1348 else if (change == QQuickItem::ItemSceneChange) {
1349 for (int i = 0; i < d->sp.size(); ++i)
1350 QQuickShapePathPrivate::get(p: d->sp[i])->dirty = QQuickShapePathPrivate::DirtyAll;
1351 d->_q_shapePathChanged();
1352 d->handleSceneChange(w: data.window);
1353 }
1354
1355 QQuickItem::itemChange(change, data);
1356}
1357
1358QSGNode *QQuickShape::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
1359{
1360 // Called on the render thread, with the gui thread blocked. We can now
1361 // safely access gui thread data.
1362 Q_D(QQuickShape);
1363
1364 if (d->renderer || d->rendererChanged) {
1365 if (!node || d->rendererChanged) {
1366 d->rendererChanged = false;
1367 delete node;
1368 node = d->createNode();
1369 }
1370 if (d->renderer)
1371 d->renderer->updateNode();
1372
1373 // TODO: only add transform node when needed (and then make sure static_cast is safe)
1374 QMatrix4x4 fillModeTransform;
1375 qreal xScale = 1.0;
1376 qreal yScale = 1.0;
1377
1378 if (d->fillMode != NoResize) {
1379 xScale = width() / implicitWidth();
1380 yScale = height() / implicitHeight();
1381
1382 if (d->fillMode == PreserveAspectFit)
1383 xScale = yScale = qMin(a: xScale, b: yScale);
1384 else if (d->fillMode == PreserveAspectCrop)
1385 xScale = yScale = qMax(a: xScale, b: yScale);
1386 fillModeTransform.scale(x: xScale, y: yScale);
1387 }
1388 if (d->horizontalAlignment != AlignLeft || d->verticalAlignment != AlignTop) {
1389 qreal tx = 0;
1390 qreal ty = 0;
1391 qreal w = xScale * implicitWidth();
1392 qreal h = yScale * implicitHeight();
1393 if (d->horizontalAlignment == AlignRight)
1394 tx = width() - w;
1395 else if (d->horizontalAlignment == AlignHCenter)
1396 tx = (width() - w) / 2;
1397 if (d->verticalAlignment == AlignBottom)
1398 ty = height() - h;
1399 else if (d->verticalAlignment == AlignVCenter)
1400 ty = (height() - h) / 2;
1401 fillModeTransform.translate(x: tx / xScale, y: ty / yScale);
1402 }
1403
1404 QSGTransformNode *transformNode = static_cast<QSGTransformNode *>(node);
1405 if (fillModeTransform != transformNode->matrix())
1406 transformNode->setMatrix(fillModeTransform);
1407 }
1408 return node;
1409}
1410
1411QQuickShape::RendererType QQuickShapePrivate::selectRendererType()
1412{
1413 QQuickShape::RendererType res = QQuickShape::UnknownRenderer;
1414 Q_Q(QQuickShape);
1415 QSGRendererInterface *ri = q->window()->rendererInterface();
1416 if (!ri)
1417 return res;
1418
1419 static const bool environmentPreferCurve =
1420 qEnvironmentVariable(varName: "QT_QUICKSHAPES_BACKEND").toLower() == QLatin1String("curverenderer");
1421
1422 switch (ri->graphicsApi()) {
1423 case QSGRendererInterface::Software:
1424 res = QQuickShape::SoftwareRenderer;
1425 break;
1426 default:
1427 if (QSGRendererInterface::isApiRhiBased(api: ri->graphicsApi())) {
1428 if (preferredType == QQuickShape::CurveRenderer || environmentPreferCurve) {
1429 res = QQuickShape::CurveRenderer;
1430 } else {
1431 res = QQuickShape::GeometryRenderer;
1432 }
1433 } else {
1434 qWarning(msg: "No path backend for this graphics API yet");
1435 }
1436 break;
1437 }
1438
1439 return res;
1440}
1441
1442// the renderer object lives on the gui thread
1443void QQuickShapePrivate::createRenderer()
1444{
1445 Q_Q(QQuickShape);
1446 QQuickShape::RendererType selectedType = selectRendererType();
1447 if (selectedType == QQuickShape::UnknownRenderer)
1448 return;
1449
1450 rendererType = selectedType;
1451 rendererChanged = true;
1452
1453 switch (selectedType) {
1454 case QQuickShape::SoftwareRenderer:
1455 renderer = new QQuickShapeSoftwareRenderer;
1456 break;
1457 case QQuickShape::GeometryRenderer:
1458 renderer = new QQuickShapeGenericRenderer(q);
1459 break;
1460 case QQuickShape::CurveRenderer:
1461 renderer = new QQuickShapeCurveRenderer(q);
1462 break;
1463 default:
1464 Q_UNREACHABLE();
1465 break;
1466 }
1467}
1468
1469// the node lives on the render thread
1470QSGNode *QQuickShapePrivate::createNode()
1471{
1472 Q_Q(QQuickShape);
1473 QSGNode *node = nullptr;
1474 if (!q->window() || !renderer)
1475 return node;
1476 QSGRendererInterface *ri = q->window()->rendererInterface();
1477 if (!ri)
1478 return node;
1479
1480 QSGNode *pathNode = nullptr;
1481 switch (ri->graphicsApi()) {
1482 case QSGRendererInterface::Software:
1483 pathNode = new QQuickShapeSoftwareRenderNode(q);
1484 static_cast<QQuickShapeSoftwareRenderer *>(renderer)->setNode(
1485 static_cast<QQuickShapeSoftwareRenderNode *>(pathNode));
1486 break;
1487 default:
1488 if (QSGRendererInterface::isApiRhiBased(api: ri->graphicsApi())) {
1489 if (rendererType == QQuickShape::CurveRenderer) {
1490 pathNode = new QSGNode;
1491 static_cast<QQuickShapeCurveRenderer *>(renderer)->setRootNode(pathNode);
1492 } else {
1493 pathNode = new QQuickShapeGenericNode;
1494 static_cast<QQuickShapeGenericRenderer *>(renderer)->setRootNode(
1495 static_cast<QQuickShapeGenericNode *>(pathNode));
1496 }
1497 } else {
1498 qWarning(msg: "No path backend for this graphics API yet");
1499 }
1500 break;
1501 }
1502
1503 // TODO: only create transform node when needed
1504 node = new QSGTransformNode;
1505 node->appendChildNode(node: pathNode);
1506
1507 return node;
1508}
1509
1510void QQuickShapePrivate::asyncShapeReady(void *data)
1511{
1512 QQuickShapePrivate *self = static_cast<QQuickShapePrivate *>(data);
1513 self->setStatus(QQuickShape::Ready);
1514 if (self->syncTimingActive)
1515 qDebug(msg: "[Shape %p] [%d] [dirty=0x%x] async update took %lld ms",
1516 self->q_func(), self->syncTimeCounter, self->syncTimingTotalDirty, self->syncTimer.elapsed());
1517}
1518
1519void QQuickShapePrivate::sync()
1520{
1521 int totalDirty = 0;
1522 syncTimingActive = QQSHAPE_LOG_TIME_DIRTY_SYNC().isDebugEnabled();
1523 if (syncTimingActive)
1524 syncTimer.start();
1525
1526 const bool useAsync = async && renderer->flags().testFlag(flag: QQuickAbstractPathRenderer::SupportsAsync);
1527 if (useAsync) {
1528 setStatus(QQuickShape::Processing);
1529 renderer->setAsyncCallback(asyncShapeReady, this);
1530 }
1531
1532 const int count = sp.size();
1533 bool countChanged = false;
1534 renderer->beginSync(totalCount: count, countChanged: &countChanged);
1535 renderer->setTriangulationScale(triangulationScale);
1536
1537 qCDebug(lcShapeSync) << "syncing" << count << "path(s)";
1538 for (int i = 0; i < count; ++i) {
1539 QQuickShapePath *p = sp[i];
1540 qCDebug(lcShapeSync) << "- syncing path:" << p;
1541 int &dirty(QQuickShapePathPrivate::get(p)->dirty);
1542 totalDirty |= dirty;
1543
1544 if (dirty & (QQuickShapePathPrivate::DirtyPath | QQuickShapePathPrivate::DirtyTrim)) {
1545 qCDebug(lcShapeSync) << " - DirtyPath";
1546 renderer->setPath(index: i, path: p);
1547 }
1548 if (dirty & QQuickShapePathPrivate::DirtyStrokeColor) {
1549 qCDebug(lcShapeSync) << " - DirtyStrokeColor:" << p->strokeColor();
1550 renderer->setStrokeColor(index: i, color: p->strokeColor());
1551 }
1552 if (dirty & QQuickShapePathPrivate::DirtyStrokeWidth)
1553 renderer->setStrokeWidth(index: i, w: p->strokeWidth());
1554 if (dirty & QQuickShapePathPrivate::DirtyFillColor)
1555 renderer->setFillColor(index: i, color: p->fillColor());
1556 if (dirty & QQuickShapePathPrivate::DirtyFillRule)
1557 renderer->setFillRule(index: i, fillRule: p->fillRule());
1558 if (dirty & QQuickShapePathPrivate::DirtyStyle) {
1559 renderer->setJoinStyle(index: i, joinStyle: p->joinStyle(), miterLimit: p->miterLimit());
1560 renderer->setCapStyle(index: i, capStyle: p->capStyle());
1561 }
1562 if (dirty & QQuickShapePathPrivate::DirtyDash)
1563 renderer->setStrokeStyle(index: i, strokeStyle: p->strokeStyle(), dashOffset: p->dashOffset(), dashPattern: p->dashPattern());
1564 if (dirty & QQuickShapePathPrivate::DirtyFillGradient)
1565 renderer->setFillGradient(index: i, gradient: p->fillGradient());
1566 if (dirty & QQuickShapePathPrivate::DirtyFillTransform)
1567 renderer->setFillTransform(index: i, transform: QQuickShapePathPrivate::get(p)->sfp.fillTransform);
1568 if (dirty & QQuickShapePathPrivate::DirtyFillItem) {
1569 if (p->fillItem() == nullptr) {
1570 renderer->setFillTextureProvider(index: i, textureProviderItem: nullptr);
1571 } else if (p->fillItem()->isTextureProvider()) {
1572 renderer->setFillTextureProvider(index: i, textureProviderItem: p->fillItem());
1573 } else {
1574 renderer->setFillTextureProvider(index: i, textureProviderItem: nullptr);
1575 qWarning() << "QQuickShape: Fill item is not texture provider";
1576 }
1577 }
1578
1579 dirty = 0;
1580 }
1581
1582 syncTimingTotalDirty = totalDirty;
1583 if (syncTimingTotalDirty)
1584 ++syncTimeCounter;
1585 else
1586 syncTimingActive = false;
1587
1588 renderer->endSync(async: useAsync);
1589
1590 if (!useAsync) {
1591 setStatus(QQuickShape::Ready);
1592 if (syncTimingActive)
1593 qDebug(msg: "[Shape %p] [%d] [dirty=0x%x] update took %lld ms",
1594 q_func(), syncTimeCounter, syncTimingTotalDirty, syncTimer.elapsed());
1595 }
1596
1597 // Must dirty the QQuickItem if something got changed, nothing
1598 // else does this for us.
1599 Q_Q(QQuickShape);
1600 if (totalDirty || countChanged)
1601 q->update();
1602}
1603
1604// ***** gradient support *****
1605
1606/*!
1607 \qmltype ShapeGradient
1608 //! \nativetype QQuickShapeGradient
1609 \inqmlmodule QtQuick.Shapes
1610 \ingroup qtquick-paths
1611 \ingroup qtquick-views
1612 \inherits Gradient
1613 \brief Base type of Shape fill gradients.
1614 \since 5.10
1615
1616 This is an abstract base class for gradients like LinearGradient and
1617 cannot be created directly. It extends \l Gradient with properties like the
1618 spread mode.
1619 */
1620
1621QQuickShapeGradient::QQuickShapeGradient(QObject *parent)
1622 : QQuickGradient(parent),
1623 m_spread(PadSpread)
1624{
1625}
1626
1627/*!
1628 \qmlproperty enumeration QtQuick.Shapes::ShapeGradient::spread
1629
1630 Specifies how the area outside the gradient area should be filled. The
1631 default value is \c ShapeGradient.PadSpread.
1632
1633 \value ShapeGradient.PadSpread
1634 The area is filled with the closest stop color.
1635
1636 \value ShapeGradient.RepeatSpread
1637 The gradient is repeated outside the gradient area.
1638
1639 \value ShapeGradient.ReflectSpread
1640 The gradient is reflected outside the gradient area.
1641 */
1642
1643QQuickShapeGradient::SpreadMode QQuickShapeGradient::spread() const
1644{
1645 return m_spread;
1646}
1647
1648void QQuickShapeGradient::setSpread(SpreadMode mode)
1649{
1650 if (m_spread != mode) {
1651 m_spread = mode;
1652 emit spreadChanged();
1653 emit updated();
1654 }
1655}
1656
1657/*!
1658 \qmltype LinearGradient
1659 //! \nativetype QQuickShapeLinearGradient
1660 \inqmlmodule QtQuick.Shapes
1661 \ingroup qtquick-paths
1662 \ingroup qtquick-views
1663 \inherits ShapeGradient
1664 \brief Linear gradient.
1665 \since 5.10
1666
1667 Linear gradients interpolate colors between start and end points in Shape
1668 items. Outside these points the gradient is either padded, reflected or
1669 repeated depending on the spread type.
1670
1671 \note LinearGradient is only supported in combination with Shape items. It
1672 is not compatible with \l Rectangle, as that only supports \l Gradient.
1673
1674 \sa QLinearGradient
1675 */
1676
1677QQuickShapeLinearGradient::QQuickShapeLinearGradient(QObject *parent)
1678 : QQuickShapeGradient(parent)
1679{
1680}
1681
1682/*!
1683 \qmlproperty real QtQuick.Shapes::LinearGradient::x1
1684 \qmlproperty real QtQuick.Shapes::LinearGradient::y1
1685 \qmlproperty real QtQuick.Shapes::LinearGradient::x2
1686 \qmlproperty real QtQuick.Shapes::LinearGradient::y2
1687
1688 These properties define the start and end points between which color
1689 interpolation occurs. By default both points are set to (0, 0).
1690 */
1691
1692qreal QQuickShapeLinearGradient::x1() const
1693{
1694 return m_start.x();
1695}
1696
1697void QQuickShapeLinearGradient::setX1(qreal v)
1698{
1699 if (m_start.x() != v) {
1700 m_start.setX(v);
1701 emit x1Changed();
1702 emit updated();
1703 }
1704}
1705
1706qreal QQuickShapeLinearGradient::y1() const
1707{
1708 return m_start.y();
1709}
1710
1711void QQuickShapeLinearGradient::setY1(qreal v)
1712{
1713 if (m_start.y() != v) {
1714 m_start.setY(v);
1715 emit y1Changed();
1716 emit updated();
1717 }
1718}
1719
1720qreal QQuickShapeLinearGradient::x2() const
1721{
1722 return m_end.x();
1723}
1724
1725void QQuickShapeLinearGradient::setX2(qreal v)
1726{
1727 if (m_end.x() != v) {
1728 m_end.setX(v);
1729 emit x2Changed();
1730 emit updated();
1731 }
1732}
1733
1734qreal QQuickShapeLinearGradient::y2() const
1735{
1736 return m_end.y();
1737}
1738
1739void QQuickShapeLinearGradient::setY2(qreal v)
1740{
1741 if (m_end.y() != v) {
1742 m_end.setY(v);
1743 emit y2Changed();
1744 emit updated();
1745 }
1746}
1747
1748/*!
1749 \qmltype RadialGradient
1750 //! \nativetype QQuickShapeRadialGradient
1751 \inqmlmodule QtQuick.Shapes
1752 \ingroup qtquick-paths
1753 \ingroup qtquick-views
1754 \inherits ShapeGradient
1755 \brief Radial gradient.
1756 \since 5.10
1757
1758 Radial gradients interpolate colors between a focal circle and a center
1759 circle in Shape items. Points outside the cone defined by the two circles
1760 will be transparent.
1761
1762 Outside the end points the gradient is either padded, reflected or repeated
1763 depending on the spread type.
1764
1765 Below is an example of a simple radial gradient. Here the colors are
1766 interpolated between the specified point and the end points on a circle
1767 specified by the radius:
1768
1769 \code
1770 fillGradient: RadialGradient {
1771 centerX: 50; centerY: 50
1772 centerRadius: 100
1773 focalX: centerX; focalY: centerY
1774 GradientStop { position: 0; color: "blue" }
1775 GradientStop { position: 0.2; color: "green" }
1776 GradientStop { position: 0.4; color: "red" }
1777 GradientStop { position: 0.6; color: "yellow" }
1778 GradientStop { position: 1; color: "cyan" }
1779 }
1780 \endcode
1781
1782 \image shape-radial-gradient.png
1783
1784 Extended radial gradients, where a separate focal circle is specified, are
1785 also supported.
1786
1787 \note RadialGradient is only supported in combination with Shape items. It
1788 is not compatible with \l Rectangle, as that only supports \l Gradient.
1789
1790 \sa QRadialGradient
1791 */
1792
1793QQuickShapeRadialGradient::QQuickShapeRadialGradient(QObject *parent)
1794 : QQuickShapeGradient(parent)
1795{
1796}
1797
1798/*!
1799 \qmlproperty real QtQuick.Shapes::RadialGradient::centerX
1800 \qmlproperty real QtQuick.Shapes::RadialGradient::centerY
1801 \qmlproperty real QtQuick.Shapes::RadialGradient::focalX
1802 \qmlproperty real QtQuick.Shapes::RadialGradient::focalY
1803
1804 These properties define the center and focal points. To specify a simple
1805 radial gradient, set focalX and focalY to the value of centerX and
1806 centerY, respectively.
1807 */
1808
1809qreal QQuickShapeRadialGradient::centerX() const
1810{
1811 return m_centerPoint.x();
1812}
1813
1814void QQuickShapeRadialGradient::setCenterX(qreal v)
1815{
1816 if (m_centerPoint.x() != v) {
1817 m_centerPoint.setX(v);
1818 emit centerXChanged();
1819 emit updated();
1820 }
1821}
1822
1823qreal QQuickShapeRadialGradient::centerY() const
1824{
1825 return m_centerPoint.y();
1826}
1827
1828void QQuickShapeRadialGradient::setCenterY(qreal v)
1829{
1830 if (m_centerPoint.y() != v) {
1831 m_centerPoint.setY(v);
1832 emit centerYChanged();
1833 emit updated();
1834 }
1835}
1836
1837/*!
1838 \qmlproperty real QtQuick.Shapes::RadialGradient::centerRadius
1839 \qmlproperty real QtQuick.Shapes::RadialGradient::focalRadius
1840
1841 These properties define the center and focal radius. For simple radial
1842 gradients, focalRadius should be set to \c 0 (the default value).
1843 */
1844
1845qreal QQuickShapeRadialGradient::centerRadius() const
1846{
1847 return m_centerRadius;
1848}
1849
1850void QQuickShapeRadialGradient::setCenterRadius(qreal v)
1851{
1852 if (m_centerRadius != v) {
1853 m_centerRadius = v;
1854 emit centerRadiusChanged();
1855 emit updated();
1856 }
1857}
1858
1859qreal QQuickShapeRadialGradient::focalX() const
1860{
1861 return m_focalPoint.x();
1862}
1863
1864void QQuickShapeRadialGradient::setFocalX(qreal v)
1865{
1866 if (m_focalPoint.x() != v) {
1867 m_focalPoint.setX(v);
1868 emit focalXChanged();
1869 emit updated();
1870 }
1871}
1872
1873qreal QQuickShapeRadialGradient::focalY() const
1874{
1875 return m_focalPoint.y();
1876}
1877
1878void QQuickShapeRadialGradient::setFocalY(qreal v)
1879{
1880 if (m_focalPoint.y() != v) {
1881 m_focalPoint.setY(v);
1882 emit focalYChanged();
1883 emit updated();
1884 }
1885}
1886
1887qreal QQuickShapeRadialGradient::focalRadius() const
1888{
1889 return m_focalRadius;
1890}
1891
1892void QQuickShapeRadialGradient::setFocalRadius(qreal v)
1893{
1894 if (m_focalRadius != v) {
1895 m_focalRadius = v;
1896 emit focalRadiusChanged();
1897 emit updated();
1898 }
1899}
1900
1901/*!
1902 \qmltype ConicalGradient
1903 //! \nativetype QQuickShapeConicalGradient
1904 \inqmlmodule QtQuick.Shapes
1905 \ingroup qtquick-paths
1906 \ingroup qtquick-views
1907 \inherits ShapeGradient
1908 \brief Conical gradient.
1909 \since 5.10
1910
1911 Conical gradients interpolate colors counter-clockwise around a center
1912 point in Shape items.
1913
1914 \note The \l{ShapeGradient::spread}{spread mode} setting has no effect for
1915 conical gradients.
1916
1917 \note ConicalGradient is only supported in combination with Shape items. It
1918 is not compatible with \l Rectangle, as that only supports \l Gradient.
1919
1920 \sa QConicalGradient
1921 */
1922
1923QQuickShapeConicalGradient::QQuickShapeConicalGradient(QObject *parent)
1924 : QQuickShapeGradient(parent)
1925{
1926}
1927
1928/*!
1929 \qmlproperty real QtQuick.Shapes::ConicalGradient::centerX
1930 \qmlproperty real QtQuick.Shapes::ConicalGradient::centerY
1931
1932 These properties define the center point of the conical gradient.
1933 */
1934
1935qreal QQuickShapeConicalGradient::centerX() const
1936{
1937 return m_centerPoint.x();
1938}
1939
1940void QQuickShapeConicalGradient::setCenterX(qreal v)
1941{
1942 if (m_centerPoint.x() != v) {
1943 m_centerPoint.setX(v);
1944 emit centerXChanged();
1945 emit updated();
1946 }
1947}
1948
1949qreal QQuickShapeConicalGradient::centerY() const
1950{
1951 return m_centerPoint.y();
1952}
1953
1954void QQuickShapeConicalGradient::setCenterY(qreal v)
1955{
1956 if (m_centerPoint.y() != v) {
1957 m_centerPoint.setY(v);
1958 emit centerYChanged();
1959 emit updated();
1960 }
1961}
1962
1963/*!
1964 \qmlproperty real QtQuick.Shapes::ConicalGradient::angle
1965
1966 This property defines the start angle for the conical gradient. The value
1967 is in degrees (0-360).
1968 */
1969
1970qreal QQuickShapeConicalGradient::angle() const
1971{
1972 return m_angle;
1973}
1974
1975void QQuickShapeConicalGradient::setAngle(qreal v)
1976{
1977 if (m_angle != v) {
1978 m_angle = v;
1979 emit angleChanged();
1980 emit updated();
1981 }
1982}
1983
1984QT_END_NAMESPACE
1985
1986#include "moc_qquickshape_p.cpp"
1987

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