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