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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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