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 "qquickpath_p.h"
5#include "qquickpath_p_p.h"
6#include "qquicksvgparser_p.h"
7
8#include <QSet>
9#include <QTime>
10
11#include <private/qbezier_p.h>
12#include <QtCore/qmath.h>
13#include <QtCore/private/qnumeric_p.h>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \qmltype PathElement
19 \nativetype QQuickPathElement
20 \inqmlmodule QtQuick
21 \ingroup qtquick-animation-paths
22 \brief PathElement is the base path type.
23
24 This type is the base for all path types. It cannot
25 be instantiated.
26
27 \sa Path, PathAttribute, PathPercent, PathLine, PathPolyline, PathQuad, PathCubic, PathArc,
28 PathAngleArc, PathCurve, PathSvg, PathRectangle
29*/
30
31/*!
32 \qmltype Path
33 \nativetype QQuickPath
34 \inqmlmodule QtQuick
35 \ingroup qtquick-animation-paths
36 \brief Defines a path for use by \l PathView and \l Shape.
37
38 A Path is composed of one or more path segments - PathLine, PathPolyline, PathQuad,
39 PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg.
40
41 The spacing of the items along the Path can be adjusted via a
42 PathPercent object.
43
44 PathAttribute allows named attributes with values to be defined
45 along the path.
46
47 Path and the other types for specifying path elements are shared between
48 \l PathView and \l Shape. The following table provides an overview of the
49 applicability of the various path elements:
50
51 \table
52 \header
53 \li Element
54 \li PathView
55 \li Shape
56 \li Shape, software
57 \row
58 \li PathMove
59 \li N/A
60 \li Yes
61 \li Yes
62 \row
63 \li PathLine
64 \li Yes
65 \li Yes
66 \li Yes
67 \row
68 \li PathPolyline
69 \li Yes
70 \li Yes
71 \li Yes
72 \row
73 \li PathMultiline
74 \li Yes
75 \li Yes
76 \li Yes
77 \row
78 \li PathQuad
79 \li Yes
80 \li Yes
81 \li Yes
82 \row
83 \li PathCubic
84 \li Yes
85 \li Yes
86 \li Yes
87 \row
88 \li PathArc
89 \li Yes
90 \li Yes
91 \li Yes
92 \row
93 \li PathAngleArc
94 \li Yes
95 \li Yes
96 \li Yes
97 \row
98 \li PathSvg
99 \li Yes
100 \li Yes
101 \li Yes
102 \row
103 \li PathRectangle
104 \li Yes
105 \li Yes
106 \li Yes
107 \row
108 \li PathAttribute
109 \li Yes
110 \li N/A
111 \li N/A
112 \row
113 \li PathPercent
114 \li Yes
115 \li N/A
116 \li N/A
117 \row
118 \li PathCurve
119 \li Yes
120 \li No
121 \li No
122 \endtable
123
124 \note Path is a non-visual type; it does not display anything on its own.
125 To draw a path, use \l Shape.
126
127 \sa PathView, Shape, PathAttribute, PathPercent, PathLine, PathPolyline, PathMove, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathRectangle
128*/
129QQuickPath::QQuickPath(QObject *parent)
130 : QObject(*(new QQuickPathPrivate), parent)
131{
132}
133
134QQuickPath::QQuickPath(QQuickPathPrivate &dd, QObject *parent)
135 : QObject(dd, parent)
136{
137}
138
139QQuickPath::~QQuickPath()
140{
141}
142
143/*!
144 \qmlproperty real QtQuick::Path::startX
145 \qmlproperty real QtQuick::Path::startY
146 These properties hold the starting position of the path.
147*/
148qreal QQuickPath::startX() const
149{
150 Q_D(const QQuickPath);
151 return d->startX.isValid() ? d->startX.value() : 0;
152}
153
154void QQuickPath::setStartX(qreal x)
155{
156 Q_D(QQuickPath);
157 if (d->startX.isValid() && qFuzzyCompare(p1: x, p2: d->startX))
158 return;
159 d->startX = x;
160 emit startXChanged();
161 processPath();
162}
163
164bool QQuickPath::hasStartX() const
165{
166 Q_D(const QQuickPath);
167 return d->startX.isValid();
168}
169
170qreal QQuickPath::startY() const
171{
172 Q_D(const QQuickPath);
173 return d->startY.isValid() ? d->startY.value() : 0;
174}
175
176void QQuickPath::setStartY(qreal y)
177{
178 Q_D(QQuickPath);
179 if (d->startY.isValid() && qFuzzyCompare(p1: y, p2: d->startY))
180 return;
181 d->startY = y;
182 emit startYChanged();
183 processPath();
184}
185
186bool QQuickPath::hasStartY() const
187{
188 Q_D(const QQuickPath);
189 return d->startY.isValid();
190}
191
192/*!
193 \qmlproperty bool QtQuick::Path::closed
194 This property holds whether the start and end of the path are identical.
195*/
196bool QQuickPath::isClosed() const
197{
198 Q_D(const QQuickPath);
199 return d->closed;
200}
201
202/*!
203 \qmlproperty list<PathElement> QtQuick::Path::pathElements
204 This property holds the objects composing the path.
205
206 \qmldefault
207
208 A path can contain the following path objects:
209 \list
210 \li \l PathLine - a straight line to a given position.
211 \li \l PathPolyline - a polyline specified as a list of coordinates.
212 \li \l PathMultiline - a list of polylines specified as a list of lists of coordinates.
213 \li \l PathQuad - a quadratic Bezier curve to a given position with a control point.
214 \li \l PathCubic - a cubic Bezier curve to a given position with two control points.
215 \li \l PathArc - an arc to a given position with a radius.
216 \li \l PathAngleArc - an arc specified by center point, radii, and angles.
217 \li \l PathSvg - a path specified as an SVG path data string.
218 \li \l PathRectangle - a rectangle with a given position and size
219 \li \l PathCurve - a point on a Catmull-Rom curve.
220 \li \l PathAttribute - an attribute at a given position in the path.
221 \li \l PathPercent - a way to spread out items along various segments of the path.
222 \endlist
223
224 \snippet qml/pathview/pathattributes.qml 2
225*/
226
227QQmlListProperty<QQuickPathElement> QQuickPath::pathElements()
228{
229 return QQmlListProperty<QQuickPathElement>(this,
230 nullptr,
231 pathElements_append,
232 pathElements_count,
233 pathElements_at,
234 pathElements_clear);
235}
236
237static QQuickPathPrivate *privatePath(QObject *object)
238{
239 QQuickPath *path = static_cast<QQuickPath*>(object);
240
241 return QQuickPathPrivate::get(path);
242}
243
244QQuickPathElement *QQuickPath::pathElements_at(QQmlListProperty<QQuickPathElement> *property, qsizetype index)
245{
246 QQuickPathPrivate *d = privatePath(object: property->object);
247
248 return d->_pathElements.at(i: index);
249}
250
251void QQuickPath::pathElements_append(QQmlListProperty<QQuickPathElement> *property, QQuickPathElement *pathElement)
252{
253 QQuickPathPrivate *d = privatePath(object: property->object);
254 QQuickPath *path = static_cast<QQuickPath*>(property->object);
255
256 d->_pathElements.append(t: pathElement);
257
258 if (d->componentComplete) {
259 QQuickCurve *curve = qobject_cast<QQuickCurve *>(object: pathElement);
260 if (curve)
261 d->_pathCurves.append(t: curve);
262 else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(object: pathElement))
263 d->_pathTexts.append(t: text);
264 else {
265 QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(object: pathElement);
266 if (attribute && !d->_attributes.contains(str: attribute->name()))
267 d->_attributes.append(t: attribute->name());
268 }
269
270 path->processPath();
271
272 connect(sender: pathElement, SIGNAL(changed()), receiver: path, SLOT(processPath()));
273 }
274}
275
276qsizetype QQuickPath::pathElements_count(QQmlListProperty<QQuickPathElement> *property)
277{
278 QQuickPathPrivate *d = privatePath(object: property->object);
279
280 return d->_pathElements.size();
281}
282
283void QQuickPath::pathElements_clear(QQmlListProperty<QQuickPathElement> *property)
284{
285 QQuickPathPrivate *d = privatePath(object: property->object);
286 QQuickPath *path = static_cast<QQuickPath*>(property->object);
287
288 path->disconnectPathElements();
289 d->_pathElements.clear();
290 d->_pathCurves.clear();
291 d->_pointCache.clear();
292 d->_pathTexts.clear();
293 d->_path.clear();
294 emit path->changed();
295}
296
297void QQuickPath::interpolate(int idx, const QString &name, qreal value)
298{
299 Q_D(QQuickPath);
300 interpolate(points&: d->_attributePoints, idx, name, value);
301}
302
303void QQuickPath::interpolate(QList<AttributePoint> &attributePoints, int idx, const QString &name, qreal value)
304{
305 if (!idx)
306 return;
307
308 qreal lastValue = 0;
309 qreal lastPercent = 0;
310 int search = idx - 1;
311 while(search >= 0) {
312 const AttributePoint &point = attributePoints.at(i: search);
313 if (point.values.contains(key: name)) {
314 lastValue = point.values.value(key: name);
315 lastPercent = point.origpercent;
316 break;
317 }
318 --search;
319 }
320
321 ++search;
322
323 const AttributePoint &curPoint = attributePoints.at(i: idx);
324
325 for (int ii = search; ii < idx; ++ii) {
326 AttributePoint &point = attributePoints[ii];
327
328 qreal val = lastValue + (value - lastValue) * (point.origpercent - lastPercent) / (curPoint.origpercent - lastPercent);
329 point.values.insert(key: name, value: val);
330 }
331}
332
333void QQuickPath::endpoint(const QString &name)
334{
335 Q_D(QQuickPath);
336 const AttributePoint &first = d->_attributePoints.first();
337 qreal val = first.values.value(key: name);
338 for (int ii = d->_attributePoints.size() - 1; ii >= 0; ii--) {
339 const AttributePoint &point = d->_attributePoints.at(i: ii);
340 if (point.values.contains(key: name)) {
341 for (int jj = ii + 1; jj < d->_attributePoints.size(); ++jj) {
342 AttributePoint &setPoint = d->_attributePoints[jj];
343 setPoint.values.insert(key: name, value: val);
344 }
345 return;
346 }
347 }
348}
349
350void QQuickPath::endpoint(QList<AttributePoint> &attributePoints, const QString &name)
351{
352 const AttributePoint &first = attributePoints.first();
353 qreal val = first.values.value(key: name);
354 for (int ii = attributePoints.size() - 1; ii >= 0; ii--) {
355 const AttributePoint &point = attributePoints.at(i: ii);
356 if (point.values.contains(key: name)) {
357 for (int jj = ii + 1; jj < attributePoints.size(); ++jj) {
358 AttributePoint &setPoint = attributePoints[jj];
359 setPoint.values.insert(key: name, value: val);
360 }
361 return;
362 }
363 }
364}
365
366void QQuickPath::processPath()
367{
368 Q_D(QQuickPath);
369
370 if (!d->componentComplete)
371 return;
372
373 if (!d->asynchronous) {
374 doProcessPath();
375 } else if (!d->processPending) {
376 d->processPending = true;
377 QMetaObject::invokeMethod(object: this, function: &QQuickPath::doProcessPath, type: Qt::QueuedConnection);
378 }
379}
380
381void QQuickPath::doProcessPath()
382{
383 Q_D(QQuickPath);
384
385 d->processPending = false;
386
387 if (!d->componentComplete)
388 return;
389
390 if (d->useCustomPath)
391 return;
392
393 d->_pointCache.clear();
394 d->prevBez.isValid = false;
395
396 if (d->isShapePath) {
397 // This path is a ShapePath, so avoid extra overhead
398 d->_path = createShapePath(startPoint: QPointF(), endPoint: QPointF(), pathLength&: d->pathLength, closed: &d->closed);
399 } else {
400 d->_path = createPath(startPoint: QPointF(), endPoint: QPointF(), attributes: d->_attributes, pathLength&: d->pathLength, attributePoints&: d->_attributePoints, closed: &d->closed);
401 }
402
403 if (d->simplify)
404 d->_path = d->_path.simplified();
405
406 emit changed();
407}
408
409inline static void scalePath(QPainterPath &path, const QSizeF &scale)
410{
411 const qreal xscale = scale.width();
412 const qreal yscale = scale.height();
413 if (xscale == 1 && yscale == 1)
414 return;
415
416 for (int i = 0; i < path.elementCount(); ++i) {
417 const QPainterPath::Element &element = path.elementAt(i);
418 path.setElementPositionAt(i, x: element.x * xscale, y: element.y * yscale);
419 }
420}
421
422QPainterPath QQuickPath::createPath(const QPointF &startPoint, const QPointF &endPoint, const QStringList &attributes, qreal &pathLength, QList<AttributePoint> &attributePoints, bool *closed)
423{
424 Q_D(QQuickPath);
425
426 pathLength = 0;
427 attributePoints.clear();
428
429 if (!d->componentComplete)
430 return QPainterPath();
431
432 QPainterPath path;
433
434 AttributePoint first;
435 for (int ii = 0; ii < attributes.size(); ++ii)
436 first.values[attributes.at(i: ii)] = 0;
437 attributePoints << first;
438
439 qreal startX = d->startX.isValid() ? d->startX.value() : startPoint.x();
440 qreal startY = d->startY.isValid() ? d->startY.value() : startPoint.y();
441 path.moveTo(x: startX, y: startY);
442
443 const QString percentString = QStringLiteral("_qfx_percent");
444
445 bool usesPercent = false;
446 int index = 0;
447 for (QQuickPathElement *pathElement : std::as_const(t&: d->_pathElements)) {
448 if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(object: pathElement)) {
449 QQuickPathData data;
450 data.index = index;
451 data.endPoint = endPoint;
452 data.curves = d->_pathCurves;
453 curve->addToPath(path, data);
454 AttributePoint p;
455 p.origpercent = path.length();
456 attributePoints << p;
457 ++index;
458 } else if (QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(object: pathElement)) {
459 AttributePoint &point = attributePoints.last();
460 point.values[attribute->name()] = attribute->value();
461 interpolate(attributePoints, idx: attributePoints.size() - 1, name: attribute->name(), value: attribute->value());
462 } else if (QQuickPathPercent *percent = qobject_cast<QQuickPathPercent *>(object: pathElement)) {
463 AttributePoint &point = attributePoints.last();
464 point.values[percentString] = percent->value();
465 interpolate(attributePoints, idx: attributePoints.size() - 1, name: percentString, value: percent->value());
466 usesPercent = true;
467 } else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(object: pathElement)) {
468 text->addToPath(path);
469 }
470 }
471
472 // Fixup end points
473 const AttributePoint &last = attributePoints.constLast();
474 for (int ii = 0; ii < attributes.size(); ++ii) {
475 if (!last.values.contains(key: attributes.at(i: ii)))
476 endpoint(attributePoints, name: attributes.at(i: ii));
477 }
478 if (usesPercent && !last.values.contains(key: percentString)) {
479 d->_attributePoints.last().values[percentString] = 1;
480 interpolate(idx: d->_attributePoints.size() - 1, name: percentString, value: 1);
481 }
482 scalePath(path, scale: d->scale);
483
484 // Adjust percent
485 qreal length = path.length();
486 qreal prevpercent = 0;
487 qreal prevorigpercent = 0;
488 for (int ii = 0; ii < attributePoints.size(); ++ii) {
489 const AttributePoint &point = attributePoints.at(i: ii);
490 if (point.values.contains(key: percentString)) { //special string for QQuickPathPercent
491 if ( ii > 0) {
492 qreal scale = (attributePoints[ii].origpercent/length - prevorigpercent) /
493 (point.values.value(key: percentString)-prevpercent);
494 attributePoints[ii].scale = scale;
495 }
496 attributePoints[ii].origpercent /= length;
497 attributePoints[ii].percent = point.values.value(key: percentString);
498 prevorigpercent = attributePoints.at(i: ii).origpercent;
499 prevpercent = attributePoints.at(i: ii).percent;
500 } else {
501 attributePoints[ii].origpercent /= length;
502 attributePoints[ii].percent = attributePoints.at(i: ii).origpercent;
503 }
504 }
505
506 if (closed) {
507 QPointF end = path.currentPosition();
508 *closed = length > 0 && startX * d->scale.width() == end.x() && startY * d->scale.height() == end.y();
509 }
510 pathLength = length;
511
512 return path;
513}
514
515QPainterPath QQuickPath::createShapePath(const QPointF &startPoint, const QPointF &endPoint, qreal &pathLength, bool *closed)
516{
517 Q_D(QQuickPath);
518
519 if (!d->componentComplete)
520 return QPainterPath();
521
522 QPainterPath path;
523
524 qreal startX = d->startX.isValid() ? d->startX.value() : startPoint.x();
525 qreal startY = d->startY.isValid() ? d->startY.value() : startPoint.y();
526 path.moveTo(x: startX, y: startY);
527
528 int index = 0;
529 for (QQuickCurve *curve : std::as_const(t&: d->_pathCurves)) {
530 QQuickPathData data;
531 data.index = index;
532 data.endPoint = endPoint;
533 data.curves = d->_pathCurves;
534 curve->addToPath(path, data);
535 ++index;
536 }
537
538 for (QQuickPathText *text : std::as_const(t&: d->_pathTexts))
539 text->addToPath(path);
540
541 if (closed) {
542 QPointF end = path.currentPosition();
543 *closed = startX == end.x() && startY == end.y();
544 }
545 scalePath(path, scale: d->scale);
546
547 // Note: Length of paths inside ShapePath is not used, so currently
548 // length is always 0. This avoids potentially heavy path.length()
549 //pathLength = path.length();
550 pathLength = 0;
551
552 return path;
553}
554
555void QQuickPath::classBegin()
556{
557 Q_D(QQuickPath);
558 d->componentComplete = false;
559}
560
561void QQuickPath::disconnectPathElements()
562{
563 Q_D(const QQuickPath);
564
565 for (QQuickPathElement *pathElement : d->_pathElements)
566 disconnect(sender: pathElement, SIGNAL(changed()), receiver: this, SLOT(processPath()));
567}
568
569void QQuickPath::connectPathElements()
570{
571 Q_D(const QQuickPath);
572
573 for (QQuickPathElement *pathElement : d->_pathElements)
574 connect(sender: pathElement, SIGNAL(changed()), receiver: this, SLOT(processPath()));
575}
576
577void QQuickPath::gatherAttributes()
578{
579 Q_D(QQuickPath);
580
581 QSet<QString> attributes;
582
583 // First gather up all the attributes
584 for (QQuickPathElement *pathElement : std::as_const(t&: d->_pathElements)) {
585 if (QQuickCurve *curve = qobject_cast<QQuickCurve *>(object: pathElement))
586 d->_pathCurves.append(t: curve);
587 else if (QQuickPathText *text = qobject_cast<QQuickPathText *>(object: pathElement))
588 d->_pathTexts.append(t: text);
589 else if (QQuickPathAttribute *attribute = qobject_cast<QQuickPathAttribute *>(object: pathElement))
590 attributes.insert(value: attribute->name());
591 }
592
593 d->_attributes = attributes.values();
594}
595
596void QQuickPath::componentComplete()
597{
598 Q_D(QQuickPath);
599 d->componentComplete = true;
600
601 gatherAttributes();
602
603 doProcessPath();
604
605 connectPathElements();
606}
607
608QPainterPath QQuickPath::path() const
609{
610 Q_D(const QQuickPath);
611 return d->_path;
612}
613
614void QQuickPath::setPath(const QPainterPath &path)
615{
616 Q_D(QQuickPath);
617 d->useCustomPath = !path.isEmpty();
618 d->_pointCache.clear();
619 d->prevBez.isValid = false;
620 d->_path = path;
621 emit changed();
622}
623
624QStringList QQuickPath::attributes() const
625{
626 Q_D(const QQuickPath);
627 if (!d->componentComplete) {
628 QSet<QString> attrs;
629
630 // First gather up all the attributes
631 for (QQuickPathElement *pathElement : d->_pathElements) {
632 if (QQuickPathAttribute *attribute =
633 qobject_cast<QQuickPathAttribute *>(object: pathElement))
634 attrs.insert(value: attribute->name());
635 }
636 return attrs.values();
637 }
638 return d->_attributes;
639}
640
641static inline QBezier nextBezier(const QPainterPath &path, int *current, qreal *bezLength, bool reverse = false)
642{
643 const int lastElement = reverse ? 0 : path.elementCount() - 1;
644 const int start = reverse ? *current - 1 : *current + 1;
645 for (int i=start; reverse ? i >= lastElement : i <= lastElement; reverse ? --i : ++i) {
646 const QPainterPath::Element &e = path.elementAt(i);
647
648 switch (e.type) {
649 case QPainterPath::MoveToElement:
650 break;
651 case QPainterPath::LineToElement:
652 {
653 QLineF line(path.elementAt(i: i-1), e);
654 *bezLength = line.length();
655 QPointF a = path.elementAt(i: i-1);
656 QPointF delta = e - a;
657 *current = i;
658 return QBezier::fromPoints(p1: a, p2: a + delta / 3, p3: a + 2 * delta / 3, p4: e);
659 }
660 case QPainterPath::CurveToElement:
661 {
662 QBezier b = QBezier::fromPoints(p1: path.elementAt(i: i-1),
663 p2: e,
664 p3: path.elementAt(i: i+1),
665 p4: path.elementAt(i: i+2));
666 *bezLength = b.length();
667 *current = i;
668 return b;
669 }
670 default:
671 break;
672 }
673 }
674 *current = lastElement;
675 *bezLength = 0;
676 return QBezier();
677}
678
679static inline int segmentCount(const QPainterPath &path, qreal pathLength)
680{
681 // In the really simple case of a single straight line we can interpolate without jitter
682 // between just two points.
683 if (path.elementCount() == 2
684 && path.elementAt(i: 0).type == QPainterPath::MoveToElement
685 && path.elementAt(i: 1).type == QPainterPath::LineToElement) {
686 return 1;
687 }
688 // more points means less jitter between items as they move along the
689 // path, but takes longer to generate
690 return qCeil(v: pathLength*5);
691}
692
693//derivative of the equation
694static inline qreal slopeAt(qreal t, qreal a, qreal b, qreal c, qreal d)
695{
696 return 3*t*t*(d - 3*c + 3*b - a) + 6*t*(c - 2*b + a) + 3*(b - a);
697}
698
699void QQuickPath::createPointCache() const
700{
701 Q_D(const QQuickPath);
702 qreal pathLength = d->pathLength;
703 if (pathLength <= 0 || qt_is_nan(d: pathLength))
704 return;
705
706 const int segments = segmentCount(path: d->_path, pathLength);
707 const int lastElement = d->_path.elementCount() - 1;
708 d->_pointCache.resize(size: segments+1);
709
710 int currElement = -1;
711 qreal bezLength = 0;
712 QBezier currBez = nextBezier(path: d->_path, current: &currElement, bezLength: &bezLength);
713 qreal currLength = bezLength;
714 qreal epc = currLength / pathLength;
715
716 for (int i = 0; i < d->_pointCache.size(); i++) {
717 //find which set we are in
718 qreal prevPercent = 0;
719 qreal prevOrigPercent = 0;
720 for (int ii = 0; ii < d->_attributePoints.size(); ++ii) {
721 qreal percent = qreal(i)/segments;
722 const AttributePoint &point = d->_attributePoints.at(i: ii);
723 if (percent < point.percent || ii == d->_attributePoints.size() - 1) { //### || is special case for very last item
724 qreal elementPercent = (percent - prevPercent);
725
726 qreal spc = prevOrigPercent + elementPercent * point.scale;
727
728 while (spc > epc) {
729 if (currElement > lastElement)
730 break;
731 currBez = nextBezier(path: d->_path, current: &currElement, bezLength: &bezLength);
732 if (bezLength == 0.0) {
733 currLength = pathLength;
734 epc = 1.0;
735 break;
736 }
737 currLength += bezLength;
738 epc = currLength / pathLength;
739 }
740 qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;
741 d->_pointCache[i] = currBez.pointAt(t: qBound(min: qreal(0), val: realT, max: qreal(1)));
742 break;
743 }
744 prevOrigPercent = point.origpercent;
745 prevPercent = point.percent;
746 }
747 }
748}
749
750void QQuickPath::invalidateSequentialHistory() const
751{
752 Q_D(const QQuickPath);
753 d->prevBez.isValid = false;
754}
755
756/*! \qmlproperty bool QtQuick::Path::simplify
757 \since 6.6
758
759 When set to true, the path will be simplified. This implies merging all subpaths that intersect,
760 creating a path where there are no self-intersections. Consecutive parallel lines will also be
761 merged. The simplified path is intended to be used with ShapePath.OddEvenFill. Bezier curves may
762 be flattened to line segments due to numerical instability of doing bezier curve intersections.
763*/
764void QQuickPath::setSimplify(bool s)
765{
766 Q_D(QQuickPath);
767 if (d->simplify == s)
768 return;
769
770 d->simplify = s;
771 processPath();
772
773 emit simplifyChanged();
774}
775
776bool QQuickPath::simplify() const
777{
778 Q_D(const QQuickPath);
779 return d->simplify;
780}
781
782bool QQuickPath::isAsynchronous() const
783{
784 Q_D(const QQuickPath);
785 return d->asynchronous;
786}
787
788void QQuickPath::setAsynchronous(bool a)
789{
790 Q_D(QQuickPath);
791 if (d->asynchronous == a)
792 return;
793
794 d->asynchronous = a;
795}
796
797/*!
798 \qmlproperty size QtQuick::Path::scale
799
800 This property holds the scale factor for the path.
801 The width and height of \a scale can be different, to
802 achieve anisotropic scaling.
803
804 \note Setting this property will not affect the border width.
805
806 \since QtQuick 2.14
807*/
808QSizeF QQuickPath::scale() const
809{
810 Q_D(const QQuickPath);
811 return d->scale;
812}
813
814void QQuickPath::setScale(const QSizeF &scale)
815{
816 Q_D(QQuickPath);
817 if (scale == d->scale)
818 return;
819 d->scale = scale;
820 emit scaleChanged();
821 processPath();
822}
823
824QPointF QQuickPath::sequentialPointAt(qreal p, qreal *angle) const
825{
826 Q_D(const QQuickPath);
827 return sequentialPointAt(path: d->_path, pathLength: d->pathLength, attributePoints: d->_attributePoints, prevBez&: d->prevBez, p, angle);
828}
829
830QPointF QQuickPath::sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle)
831{
832 Q_ASSERT(p >= 0.0 && p <= 1.0);
833
834 if (!prevBez.isValid)
835 return p > .5 ? backwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle) :
836 forwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle);
837
838 return p < prevBez.p ? backwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle) :
839 forwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle);
840}
841
842QPointF QQuickPath::forwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle)
843{
844 if (pathLength <= 0 || qt_is_nan(d: pathLength))
845 return path.pointAtPercent(t: 0); //expensive?
846
847 const int lastElement = path.elementCount() - 1;
848 bool haveCachedBez = prevBez.isValid;
849 int currElement = haveCachedBez ? prevBez.element : -1;
850 qreal bezLength = haveCachedBez ? prevBez.bezLength : 0;
851 QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, current: &currElement, bezLength: &bezLength);
852 qreal currLength = haveCachedBez ? prevBez.currLength : bezLength;
853 qreal epc = currLength / pathLength;
854
855 //find which set we are in
856 qreal prevPercent = 0;
857 qreal prevOrigPercent = 0;
858 for (int ii = 0; ii < attributePoints.size(); ++ii) {
859 qreal percent = p;
860 const AttributePoint &point = attributePoints.at(i: ii);
861 if (percent < point.percent || ii == attributePoints.size() - 1) {
862 qreal elementPercent = (percent - prevPercent);
863
864 qreal spc = prevOrigPercent + elementPercent * point.scale;
865
866 while (spc > epc) {
867 Q_ASSERT(!(currElement > lastElement));
868 Q_UNUSED(lastElement);
869 currBez = nextBezier(path, current: &currElement, bezLength: &bezLength);
870 currLength += bezLength;
871 epc = currLength / pathLength;
872 }
873 prevBez.element = currElement;
874 prevBez.bezLength = bezLength;
875 prevBez.currLength = currLength;
876 prevBez.bezier = currBez;
877 prevBez.p = p;
878 prevBez.isValid = true;
879
880 qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;
881
882 if (angle) {
883 qreal m1 = slopeAt(t: realT, a: currBez.x1, b: currBez.x2, c: currBez.x3, d: currBez.x4);
884 qreal m2 = slopeAt(t: realT, a: currBez.y1, b: currBez.y2, c: currBez.y3, d: currBez.y4);
885 *angle = QLineF(0, 0, m1, m2).angle();
886 }
887
888 return currBez.pointAt(t: qBound(min: qreal(0), val: realT, max: qreal(1)));
889 }
890 prevOrigPercent = point.origpercent;
891 prevPercent = point.percent;
892 }
893
894 return QPointF(0,0);
895}
896
897//ideally this should be merged with forwardsPointAt
898QPointF QQuickPath::backwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList<AttributePoint> &attributePoints, QQuickCachedBezier &prevBez, qreal p, qreal *angle)
899{
900 if (pathLength <= 0 || qt_is_nan(d: pathLength))
901 return path.pointAtPercent(t: 0);
902
903 const int firstElement = 1; //element 0 is always a MoveTo, which we ignore
904 bool haveCachedBez = prevBez.isValid;
905 int currElement = haveCachedBez ? prevBez.element : path.elementCount();
906 qreal bezLength = haveCachedBez ? prevBez.bezLength : 0;
907 QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, current: &currElement, bezLength: &bezLength, reverse: true /*reverse*/);
908 qreal currLength = haveCachedBez ? prevBez.currLength : pathLength;
909 qreal prevLength = currLength - bezLength;
910 qreal epc = prevLength / pathLength;
911
912 for (int ii = attributePoints.size() - 1; ii > 0; --ii) {
913 qreal percent = p;
914 const AttributePoint &point = attributePoints.at(i: ii);
915 const AttributePoint &prevPoint = attributePoints.at(i: ii-1);
916 if (percent > prevPoint.percent || ii == 1) {
917 qreal elementPercent = (percent - prevPoint.percent);
918
919 qreal spc = prevPoint.origpercent + elementPercent * point.scale;
920
921 while (spc < epc) {
922 Q_ASSERT(!(currElement < firstElement));
923 Q_UNUSED(firstElement);
924 currBez = nextBezier(path, current: &currElement, bezLength: &bezLength, reverse: true /*reverse*/);
925 //special case for first element is to avoid floating point math
926 //causing an epc that never hits 0.
927 currLength = (currElement == firstElement) ? bezLength : prevLength;
928 prevLength = currLength - bezLength;
929 epc = prevLength / pathLength;
930 }
931 prevBez.element = currElement;
932 prevBez.bezLength = bezLength;
933 prevBez.currLength = currLength;
934 prevBez.bezier = currBez;
935 prevBez.p = p;
936 prevBez.isValid = true;
937
938 qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength;
939
940 if (angle) {
941 qreal m1 = slopeAt(t: realT, a: currBez.x1, b: currBez.x2, c: currBez.x3, d: currBez.x4);
942 qreal m2 = slopeAt(t: realT, a: currBez.y1, b: currBez.y2, c: currBez.y3, d: currBez.y4);
943 *angle = QLineF(0, 0, m1, m2).angle();
944 }
945
946 return currBez.pointAt(t: qBound(min: qreal(0), val: realT, max: qreal(1)));
947 }
948 }
949
950 return QPointF(0,0);
951}
952
953/*!
954 \qmlmethod point Path::pointAtPercent(real t)
955
956 Returns the point at the percentage \a t of the current path.
957 The argument \a t has to be between 0 and 1.
958
959 \note Similarly to other percent methods in \l QPainterPath,
960 the percentage measurement is not linear with regards to the length,
961 if curves are present in the path.
962 When curves are present, the percentage argument is mapped to the \c t
963 parameter of the Bezier equations.
964
965 \sa QPainterPath::pointAtPercent()
966
967 \since QtQuick 2.14
968*/
969QPointF QQuickPath::pointAtPercent(qreal t) const
970{
971 Q_D(const QQuickPath);
972 if (d->isShapePath) // this since ShapePath does not calculate the length at all,
973 return d->_path.pointAtPercent(t); // in order to be faster.
974
975 if (d->_pointCache.isEmpty()) {
976 createPointCache();
977 if (d->_pointCache.isEmpty())
978 return QPointF();
979 }
980
981 const int segmentCount = d->_pointCache.size() - 1;
982 qreal idxf = t*segmentCount;
983 int idx1 = qFloor(v: idxf);
984 qreal delta = idxf - idx1;
985 if (idx1 > segmentCount)
986 idx1 = segmentCount;
987 else if (idx1 < 0)
988 idx1 = 0;
989
990 if (delta == 0.0)
991 return d->_pointCache.at(i: idx1);
992
993 // interpolate between the two points.
994 int idx2 = qCeil(v: idxf);
995 if (idx2 > segmentCount)
996 idx2 = segmentCount;
997 else if (idx2 < 0)
998 idx2 = 0;
999
1000 QPointF p1 = d->_pointCache.at(i: idx1);
1001 QPointF p2 = d->_pointCache.at(i: idx2);
1002 QPointF pos = p1 * (1.0-delta) + p2 * delta;
1003
1004 return pos;
1005}
1006
1007qreal QQuickPath::attributeAt(const QString &name, qreal percent) const
1008{
1009 Q_D(const QQuickPath);
1010 if (percent < 0 || percent > 1)
1011 return 0;
1012
1013 for (int ii = 0; ii < d->_attributePoints.size(); ++ii) {
1014 const AttributePoint &point = d->_attributePoints.at(i: ii);
1015
1016 if (point.percent == percent) {
1017 return point.values.value(key: name);
1018 } else if (point.percent > percent) {
1019 qreal lastValue =
1020 ii?(d->_attributePoints.at(i: ii - 1).values.value(key: name)):0;
1021 qreal lastPercent =
1022 ii?(d->_attributePoints.at(i: ii - 1).percent):0;
1023 qreal curValue = point.values.value(key: name);
1024 qreal curPercent = point.percent;
1025
1026 return lastValue + (curValue - lastValue) * (percent - lastPercent) / (curPercent - lastPercent);
1027 }
1028 }
1029
1030 return 0;
1031}
1032
1033/****************************************************************************/
1034
1035qreal QQuickCurve::x() const
1036{
1037 return _x.isValid() ? _x.value() : 0;
1038}
1039
1040void QQuickCurve::setX(qreal x)
1041{
1042 if (!_x.isValid() || _x != x) {
1043 _x = x;
1044 emit xChanged();
1045 emit changed();
1046 }
1047}
1048
1049bool QQuickCurve::hasX()
1050{
1051 return _x.isValid();
1052}
1053
1054qreal QQuickCurve::y() const
1055{
1056 return _y.isValid() ? _y.value() : 0;
1057}
1058
1059void QQuickCurve::setY(qreal y)
1060{
1061 if (!_y.isValid() || _y != y) {
1062 _y = y;
1063 emit yChanged();
1064 emit changed();
1065 }
1066}
1067
1068bool QQuickCurve::hasY()
1069{
1070 return _y.isValid();
1071}
1072
1073qreal QQuickCurve::relativeX() const
1074{
1075 return _relativeX;
1076}
1077
1078void QQuickCurve::setRelativeX(qreal x)
1079{
1080 if (!_relativeX.isValid() || _relativeX != x) {
1081 _relativeX = x;
1082 emit relativeXChanged();
1083 emit changed();
1084 }
1085}
1086
1087bool QQuickCurve::hasRelativeX()
1088{
1089 return _relativeX.isValid();
1090}
1091
1092qreal QQuickCurve::relativeY() const
1093{
1094 return _relativeY;
1095}
1096
1097void QQuickCurve::setRelativeY(qreal y)
1098{
1099 if (!_relativeY.isValid() || _relativeY != y) {
1100 _relativeY = y;
1101 emit relativeYChanged();
1102 emit changed();
1103 }
1104}
1105
1106bool QQuickCurve::hasRelativeY()
1107{
1108 return _relativeY.isValid();
1109}
1110
1111/****************************************************************************/
1112
1113/*!
1114 \qmltype PathAttribute
1115 \nativetype QQuickPathAttribute
1116 \inqmlmodule QtQuick
1117 \ingroup qtquick-animation-paths
1118 \brief Specifies how to set an attribute at a given position in a Path.
1119
1120 The PathAttribute object allows attributes consisting of a name and
1121 a value to be specified for various points along a path. The
1122 attributes are exposed to the delegate as
1123 \l{Attached Properties and Attached Signal Handlers} {Attached Properties}.
1124 The value of an attribute at any particular point along the path is interpolated
1125 from the PathAttributes bounding that point.
1126
1127 The example below shows a path with the items scaled to 30% with
1128 opacity 50% at the top of the path and scaled 100% with opacity
1129 100% at the bottom. Note the use of the PathView.iconScale and
1130 PathView.iconOpacity attached properties to set the scale and opacity
1131 of the delegate.
1132
1133 \table
1134 \row
1135 \li \image declarative-pathattribute.png
1136 \li
1137 \snippet qml/pathview/pathattributes.qml 0
1138 (see the PathView documentation for the specification of ContactModel.qml
1139 used for ContactModel above.)
1140 \endtable
1141
1142
1143 \sa Path
1144*/
1145
1146/*!
1147 \qmlproperty string QtQuick::PathAttribute::name
1148 This property holds the name of the attribute to change.
1149
1150 This attribute will be available to the delegate as PathView.<name>
1151
1152 Note that using an existing Item property name such as "opacity" as an
1153 attribute is allowed. This is because path attributes add a new
1154 \l{Attached Properties and Attached Signal Handlers} {Attached Property}
1155 which in no way clashes with existing properties.
1156*/
1157
1158/*!
1159 the name of the attribute to change.
1160*/
1161
1162QString QQuickPathAttribute::name() const
1163{
1164 return _name;
1165}
1166
1167void QQuickPathAttribute::setName(const QString &name)
1168{
1169 if (_name == name)
1170 return;
1171 _name = name;
1172 emit nameChanged();
1173}
1174
1175/*!
1176 \qmlproperty real QtQuick::PathAttribute::value
1177 This property holds the value for the attribute.
1178
1179 The value specified can be used to influence the visual appearance
1180 of an item along the path. For example, the following Path specifies
1181 an attribute named \e itemRotation, which has the value \e 0 at the
1182 beginning of the path, and the value 90 at the end of the path.
1183
1184 \qml
1185 Path {
1186 startX: 0
1187 startY: 0
1188 PathAttribute { name: "itemRotation"; value: 0 }
1189 PathLine { x: 100; y: 100 }
1190 PathAttribute { name: "itemRotation"; value: 90 }
1191 }
1192 \endqml
1193
1194 In our delegate, we can then bind the \e rotation property to the
1195 \l{Attached Properties and Attached Signal Handlers} {Attached Property}
1196 \e PathView.itemRotation created for this attribute.
1197
1198 \qml
1199 Rectangle {
1200 width: 10; height: 10
1201 rotation: PathView.itemRotation
1202 }
1203 \endqml
1204
1205 As each item is positioned along the path, it will be rotated accordingly:
1206 an item at the beginning of the path with be not be rotated, an item at
1207 the end of the path will be rotated 90 degrees, and an item mid-way along
1208 the path will be rotated 45 degrees.
1209*/
1210
1211/*!
1212 the new value of the attribute.
1213*/
1214qreal QQuickPathAttribute::value() const
1215{
1216 return _value;
1217}
1218
1219void QQuickPathAttribute::setValue(qreal value)
1220{
1221 if (_value != value) {
1222 _value = value;
1223 emit valueChanged();
1224 emit changed();
1225 }
1226}
1227
1228/****************************************************************************/
1229
1230/*!
1231 \qmltype PathLine
1232 \nativetype QQuickPathLine
1233 \inqmlmodule QtQuick
1234 \ingroup qtquick-animation-paths
1235 \brief Defines a straight line.
1236
1237 The example below creates a path consisting of a straight line from
1238 0,100 to 200,100:
1239
1240 \qml
1241 Path {
1242 startX: 0; startY: 100
1243 PathLine { x: 200; y: 100 }
1244 }
1245 \endqml
1246
1247 \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline, PathRectangle
1248*/
1249
1250/*!
1251 \qmlproperty real QtQuick::PathLine::x
1252 \qmlproperty real QtQuick::PathLine::y
1253
1254 Defines the end point of the line.
1255
1256 \sa relativeX, relativeY
1257*/
1258
1259/*!
1260 \qmlproperty real QtQuick::PathLine::relativeX
1261 \qmlproperty real QtQuick::PathLine::relativeY
1262
1263 Defines the end point of the line relative to its start.
1264
1265 If both a relative and absolute end position are specified for a single axis, the relative
1266 position will be used.
1267
1268 Relative and absolute positions can be mixed, for example it is valid to set a relative x
1269 and an absolute y.
1270
1271 \sa x, y
1272*/
1273
1274inline QPointF positionForCurve(const QQuickPathData &data, const QPointF &prevPoint)
1275{
1276 QQuickCurve *curve = data.curves.at(i: data.index);
1277 bool isEnd = data.index == data.curves.size() - 1;
1278 return QPointF(curve->hasRelativeX() ? prevPoint.x() + curve->relativeX() : !isEnd || curve->hasX() ? curve->x() : data.endPoint.x(),
1279 curve->hasRelativeY() ? prevPoint.y() + curve->relativeY() : !isEnd || curve->hasY() ? curve->y() : data.endPoint.y());
1280}
1281
1282void QQuickPathLine::addToPath(QPainterPath &path, const QQuickPathData &data)
1283{
1284 path.lineTo(p: positionForCurve(data, prevPoint: path.currentPosition()));
1285}
1286
1287/****************************************************************************/
1288
1289/*!
1290 \qmltype PathMove
1291 \nativetype QQuickPathMove
1292 \inqmlmodule QtQuick
1293 \ingroup qtquick-animation-paths
1294 \brief Moves the Path's position.
1295
1296 The example below creates a path consisting of two horizontal lines with
1297 some empty space between them. All three segments have a width of 100:
1298
1299 \qml
1300 Path {
1301 startX: 0; startY: 100
1302 PathLine { relativeX: 100; y: 100 }
1303 PathMove { relativeX: 100; y: 100 }
1304 PathLine { relativeX: 100; y: 100 }
1305 }
1306 \endqml
1307
1308 \note PathMove should not be used in a Path associated with a PathView. Use
1309 PathLine instead. For ShapePath however it is important to distinguish
1310 between the operations of drawing a straight line and moving the path
1311 position without drawing anything.
1312
1313 \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathLine
1314*/
1315
1316/*!
1317 \qmlproperty real QtQuick::PathMove::x
1318 \qmlproperty real QtQuick::PathMove::y
1319
1320 Defines the position to move to.
1321
1322 \sa relativeX, relativeY
1323*/
1324
1325/*!
1326 \qmlproperty real QtQuick::PathMove::relativeX
1327 \qmlproperty real QtQuick::PathMove::relativeY
1328
1329 Defines the position to move to relative to its start.
1330
1331 If both a relative and absolute end position are specified for a single axis, the relative
1332 position will be used.
1333
1334 Relative and absolute positions can be mixed, for example it is valid to set a relative x
1335 and an absolute y.
1336
1337 \sa x, y
1338*/
1339
1340void QQuickPathMove::addToPath(QPainterPath &path, const QQuickPathData &data)
1341{
1342 path.moveTo(p: positionForCurve(data, prevPoint: path.currentPosition()));
1343}
1344
1345/****************************************************************************/
1346
1347/*!
1348 \qmltype PathQuad
1349 \nativetype QQuickPathQuad
1350 \inqmlmodule QtQuick
1351 \ingroup qtquick-animation-paths
1352 \brief Defines a quadratic Bezier curve with a control point.
1353
1354 The following QML produces the path shown below:
1355 \table
1356 \row
1357 \li \image declarative-pathquad.png
1358 \li
1359 \qml
1360 Path {
1361 startX: 0; startY: 0
1362 PathQuad { x: 200; y: 0; controlX: 100; controlY: 150 }
1363 }
1364 \endqml
1365 \endtable
1366
1367 \sa Path, PathCubic, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg
1368*/
1369
1370/*!
1371 \qmlproperty real QtQuick::PathQuad::x
1372 \qmlproperty real QtQuick::PathQuad::y
1373
1374 Defines the end point of the curve.
1375
1376 \sa relativeX, relativeY
1377*/
1378
1379/*!
1380 \qmlproperty real QtQuick::PathQuad::relativeX
1381 \qmlproperty real QtQuick::PathQuad::relativeY
1382
1383 Defines the end point of the curve relative to its start.
1384
1385 If both a relative and absolute end position are specified for a single axis, the relative
1386 position will be used.
1387
1388 Relative and absolute positions can be mixed, for example it is valid to set a relative x
1389 and an absolute y.
1390
1391 \sa x, y
1392*/
1393
1394/*!
1395 \qmlproperty real QtQuick::PathQuad::controlX
1396 \qmlproperty real QtQuick::PathQuad::controlY
1397
1398 Defines the position of the control point.
1399*/
1400
1401/*!
1402 the x position of the control point.
1403*/
1404qreal QQuickPathQuad::controlX() const
1405{
1406 return _controlX;
1407}
1408
1409void QQuickPathQuad::setControlX(qreal x)
1410{
1411 if (_controlX != x) {
1412 _controlX = x;
1413 emit controlXChanged();
1414 emit changed();
1415 }
1416}
1417
1418
1419/*!
1420 the y position of the control point.
1421*/
1422qreal QQuickPathQuad::controlY() const
1423{
1424 return _controlY;
1425}
1426
1427void QQuickPathQuad::setControlY(qreal y)
1428{
1429 if (_controlY != y) {
1430 _controlY = y;
1431 emit controlYChanged();
1432 emit changed();
1433 }
1434}
1435
1436/*!
1437 \qmlproperty real QtQuick::PathQuad::relativeControlX
1438 \qmlproperty real QtQuick::PathQuad::relativeControlY
1439
1440 Defines the position of the control point relative to the curve's start.
1441
1442 If both a relative and absolute control position are specified for a single axis, the relative
1443 position will be used.
1444
1445 Relative and absolute positions can be mixed, for example it is valid to set a relative control x
1446 and an absolute control y.
1447
1448 \sa controlX, controlY
1449*/
1450
1451qreal QQuickPathQuad::relativeControlX() const
1452{
1453 return _relativeControlX;
1454}
1455
1456void QQuickPathQuad::setRelativeControlX(qreal x)
1457{
1458 if (!_relativeControlX.isValid() || _relativeControlX != x) {
1459 _relativeControlX = x;
1460 emit relativeControlXChanged();
1461 emit changed();
1462 }
1463}
1464
1465bool QQuickPathQuad::hasRelativeControlX()
1466{
1467 return _relativeControlX.isValid();
1468}
1469
1470qreal QQuickPathQuad::relativeControlY() const
1471{
1472 return _relativeControlY;
1473}
1474
1475void QQuickPathQuad::setRelativeControlY(qreal y)
1476{
1477 if (!_relativeControlY.isValid() || _relativeControlY != y) {
1478 _relativeControlY = y;
1479 emit relativeControlYChanged();
1480 emit changed();
1481 }
1482}
1483
1484bool QQuickPathQuad::hasRelativeControlY()
1485{
1486 return _relativeControlY.isValid();
1487}
1488
1489void QQuickPathQuad::addToPath(QPainterPath &path, const QQuickPathData &data)
1490{
1491 const QPointF &prevPoint = path.currentPosition();
1492 QPointF controlPoint(hasRelativeControlX() ? prevPoint.x() + relativeControlX() : controlX(),
1493 hasRelativeControlY() ? prevPoint.y() + relativeControlY() : controlY());
1494 path.quadTo(ctrlPt: controlPoint, endPt: positionForCurve(data, prevPoint: path.currentPosition()));
1495}
1496
1497/****************************************************************************/
1498
1499/*!
1500 \qmltype PathCubic
1501 \nativetype QQuickPathCubic
1502 \inqmlmodule QtQuick
1503 \ingroup qtquick-animation-paths
1504 \brief Defines a cubic Bezier curve with two control points.
1505
1506 The following QML produces the path shown below:
1507 \table
1508 \row
1509 \li \image declarative-pathcubic.png
1510 \li
1511 \qml
1512 Path {
1513 startX: 20; startY: 0
1514 PathCubic {
1515 x: 180; y: 0
1516 control1X: -10; control1Y: 90
1517 control2X: 210; control2Y: 90
1518 }
1519 }
1520 \endqml
1521 \endtable
1522
1523 \sa Path, PathQuad, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg, PathRectangle
1524*/
1525
1526/*!
1527 \qmlproperty real QtQuick::PathCubic::x
1528 \qmlproperty real QtQuick::PathCubic::y
1529
1530 Defines the end point of the curve.
1531
1532 \sa relativeX, relativeY
1533*/
1534
1535/*!
1536 \qmlproperty real QtQuick::PathCubic::relativeX
1537 \qmlproperty real QtQuick::PathCubic::relativeY
1538
1539 Defines the end point of the curve relative to its start.
1540
1541 If both a relative and absolute end position are specified for a single axis, the relative
1542 position will be used.
1543
1544 Relative and absolute positions can be mixed, for example it is valid to set a relative x
1545 and an absolute y.
1546
1547 \sa x, y
1548*/
1549
1550/*!
1551 \qmlproperty real QtQuick::PathCubic::control1X
1552 \qmlproperty real QtQuick::PathCubic::control1Y
1553
1554 Defines the position of the first control point.
1555*/
1556qreal QQuickPathCubic::control1X() const
1557{
1558 return _control1X;
1559}
1560
1561void QQuickPathCubic::setControl1X(qreal x)
1562{
1563 if (_control1X != x) {
1564 _control1X = x;
1565 emit control1XChanged();
1566 emit changed();
1567 }
1568}
1569
1570qreal QQuickPathCubic::control1Y() const
1571{
1572 return _control1Y;
1573}
1574
1575void QQuickPathCubic::setControl1Y(qreal y)
1576{
1577 if (_control1Y != y) {
1578 _control1Y = y;
1579 emit control1YChanged();
1580 emit changed();
1581 }
1582}
1583
1584/*!
1585 \qmlproperty real QtQuick::PathCubic::control2X
1586 \qmlproperty real QtQuick::PathCubic::control2Y
1587
1588 Defines the position of the second control point.
1589*/
1590qreal QQuickPathCubic::control2X() const
1591{
1592 return _control2X;
1593}
1594
1595void QQuickPathCubic::setControl2X(qreal x)
1596{
1597 if (_control2X != x) {
1598 _control2X = x;
1599 emit control2XChanged();
1600 emit changed();
1601 }
1602}
1603
1604qreal QQuickPathCubic::control2Y() const
1605{
1606 return _control2Y;
1607}
1608
1609void QQuickPathCubic::setControl2Y(qreal y)
1610{
1611 if (_control2Y != y) {
1612 _control2Y = y;
1613 emit control2YChanged();
1614 emit changed();
1615 }
1616}
1617
1618/*!
1619 \qmlproperty real QtQuick::PathCubic::relativeControl1X
1620 \qmlproperty real QtQuick::PathCubic::relativeControl1Y
1621 \qmlproperty real QtQuick::PathCubic::relativeControl2X
1622 \qmlproperty real QtQuick::PathCubic::relativeControl2Y
1623
1624 Defines the positions of the control points relative to the curve's start.
1625
1626 If both a relative and absolute control position are specified for a control point's axis, the relative
1627 position will be used.
1628
1629 Relative and absolute positions can be mixed, for example it is valid to set a relative control1 x
1630 and an absolute control1 y.
1631
1632 \sa control1X, control1Y, control2X, control2Y
1633*/
1634
1635qreal QQuickPathCubic::relativeControl1X() const
1636{
1637 return _relativeControl1X;
1638}
1639
1640void QQuickPathCubic::setRelativeControl1X(qreal x)
1641{
1642 if (!_relativeControl1X.isValid() || _relativeControl1X != x) {
1643 _relativeControl1X = x;
1644 emit relativeControl1XChanged();
1645 emit changed();
1646 }
1647}
1648
1649bool QQuickPathCubic::hasRelativeControl1X()
1650{
1651 return _relativeControl1X.isValid();
1652}
1653
1654qreal QQuickPathCubic::relativeControl1Y() const
1655{
1656 return _relativeControl1Y;
1657}
1658
1659void QQuickPathCubic::setRelativeControl1Y(qreal y)
1660{
1661 if (!_relativeControl1Y.isValid() || _relativeControl1Y != y) {
1662 _relativeControl1Y = y;
1663 emit relativeControl1YChanged();
1664 emit changed();
1665 }
1666}
1667
1668bool QQuickPathCubic::hasRelativeControl1Y()
1669{
1670 return _relativeControl1Y.isValid();
1671}
1672
1673qreal QQuickPathCubic::relativeControl2X() const
1674{
1675 return _relativeControl2X;
1676}
1677
1678void QQuickPathCubic::setRelativeControl2X(qreal x)
1679{
1680 if (!_relativeControl2X.isValid() || _relativeControl2X != x) {
1681 _relativeControl2X = x;
1682 emit relativeControl2XChanged();
1683 emit changed();
1684 }
1685}
1686
1687bool QQuickPathCubic::hasRelativeControl2X()
1688{
1689 return _relativeControl2X.isValid();
1690}
1691
1692qreal QQuickPathCubic::relativeControl2Y() const
1693{
1694 return _relativeControl2Y;
1695}
1696
1697void QQuickPathCubic::setRelativeControl2Y(qreal y)
1698{
1699 if (!_relativeControl2Y.isValid() || _relativeControl2Y != y) {
1700 _relativeControl2Y = y;
1701 emit relativeControl2YChanged();
1702 emit changed();
1703 }
1704}
1705
1706bool QQuickPathCubic::hasRelativeControl2Y()
1707{
1708 return _relativeControl2Y.isValid();
1709}
1710
1711void QQuickPathCubic::addToPath(QPainterPath &path, const QQuickPathData &data)
1712{
1713 const QPointF &prevPoint = path.currentPosition();
1714 QPointF controlPoint1(hasRelativeControl1X() ? prevPoint.x() + relativeControl1X() : control1X(),
1715 hasRelativeControl1Y() ? prevPoint.y() + relativeControl1Y() : control1Y());
1716 QPointF controlPoint2(hasRelativeControl2X() ? prevPoint.x() + relativeControl2X() : control2X(),
1717 hasRelativeControl2Y() ? prevPoint.y() + relativeControl2Y() : control2Y());
1718 path.cubicTo(ctrlPt1: controlPoint1, ctrlPt2: controlPoint2, endPt: positionForCurve(data, prevPoint: path.currentPosition()));
1719}
1720
1721/****************************************************************************/
1722
1723/*!
1724 \qmltype PathCurve
1725 \nativetype QQuickPathCatmullRomCurve
1726 \inqmlmodule QtQuick
1727 \ingroup qtquick-animation-paths
1728 \brief Defines a point on a Catmull-Rom curve.
1729
1730 PathCurve provides an easy way to specify a curve passing directly through a set of points.
1731 Typically multiple PathCurves are used in a series, as the following example demonstrates:
1732
1733 \snippet qml/path/basiccurve.qml 0
1734
1735 This example produces the following path (with the starting point and PathCurve points
1736 highlighted in red):
1737
1738 \image declarative-pathcurve.png
1739
1740 \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathSvg
1741*/
1742
1743/*!
1744 \qmlproperty real QtQuick::PathCurve::x
1745 \qmlproperty real QtQuick::PathCurve::y
1746
1747 Defines the end point of the curve.
1748
1749 \sa relativeX, relativeY
1750*/
1751
1752/*!
1753 \qmlproperty real QtQuick::PathCurve::relativeX
1754 \qmlproperty real QtQuick::PathCurve::relativeY
1755
1756 Defines the end point of the curve relative to its start.
1757
1758 If both a relative and absolute end position are specified for a single axis, the relative
1759 position will be used.
1760
1761 Relative and absolute positions can be mixed, for example it is valid to set a relative x
1762 and an absolute y.
1763
1764 \sa x, y
1765*/
1766
1767inline QPointF previousPathPosition(const QPainterPath &path)
1768{
1769 int count = path.elementCount();
1770 if (count < 1)
1771 return QPointF();
1772
1773 int index = path.elementAt(i: count-1).type == QPainterPath::CurveToDataElement ? count - 4 : count - 2;
1774 return index > -1 ? QPointF(path.elementAt(i: index)) : path.pointAtPercent(t: 0);
1775}
1776
1777void QQuickPathCatmullRomCurve::addToPath(QPainterPath &path, const QQuickPathData &data)
1778{
1779 //here we convert catmull-rom spline to bezier for use in QPainterPath.
1780 //basic conversion algorithm:
1781 // catmull-rom points * inverse bezier matrix * catmull-rom matrix = bezier points
1782 //each point in the catmull-rom spline produces a bezier endpoint + 2 control points
1783 //calculations for each point use a moving window of 4 points
1784 // (previous 2 points + current point + next point)
1785 QPointF prevFar, prev, point, next;
1786
1787 //get previous points
1788 int index = data.index - 1;
1789 QQuickCurve *curve = index == -1 ? 0 : data.curves.at(i: index);
1790 if (qobject_cast<QQuickPathCatmullRomCurve*>(object: curve)) {
1791 prev = path.currentPosition();
1792 prevFar = previousPathPosition(path);
1793 } else {
1794 prev = path.currentPosition();
1795 bool prevFarSet = false;
1796 if (index == -1 && data.curves.size() > 1) {
1797 if (qobject_cast<QQuickPathCatmullRomCurve*>(object: data.curves.at(i: data.curves.size()-1))) {
1798 //TODO: profile and optimize
1799 QPointF pos = prev;
1800 QQuickPathData loopData;
1801 loopData.endPoint = data.endPoint;
1802 loopData.curves = data.curves;
1803 for (int i = data.index; i < data.curves.size(); ++i) {
1804 loopData.index = i;
1805 pos = positionForCurve(data: loopData, prevPoint: pos);
1806 if (i == data.curves.size()-2)
1807 prevFar = pos;
1808 }
1809 if (pos == QPointF(path.elementAt(i: 0))) {
1810 //this is a closed path starting and ending with catmull-rom segments.
1811 //we try to smooth the join point
1812 prevFarSet = true;
1813 }
1814 }
1815 }
1816 if (!prevFarSet)
1817 prevFar = prev;
1818 }
1819
1820 //get current point
1821 point = positionForCurve(data, prevPoint: path.currentPosition());
1822
1823 //get next point
1824 index = data.index + 1;
1825 if (index < data.curves.size() && qobject_cast<QQuickPathCatmullRomCurve*>(object: data.curves.at(i: index))) {
1826 QQuickPathData nextData;
1827 nextData.index = index;
1828 nextData.endPoint = data.endPoint;
1829 nextData.curves = data.curves;
1830 next = positionForCurve(data: nextData, prevPoint: point);
1831 } else {
1832 if (point == QPointF(path.elementAt(i: 0)) && qobject_cast<QQuickPathCatmullRomCurve*>(object: data.curves.at(i: 0)) && path.elementCount() >= 3) {
1833 //this is a closed path starting and ending with catmull-rom segments.
1834 //we try to smooth the join point
1835 next = QPointF(path.elementAt(i: 3)); //the first catmull-rom point
1836 } else
1837 next = point;
1838 }
1839
1840 /*
1841 full conversion matrix (inverse bezier * catmull-rom):
1842 0.000, 1.000, 0.000, 0.000,
1843 -0.167, 1.000, 0.167, 0.000,
1844 0.000, 0.167, 1.000, -0.167,
1845 0.000, 0.000, 1.000, 0.000
1846
1847 conversion doesn't require full matrix multiplication,
1848 so below we simplify
1849 */
1850 QPointF control1(prevFar.x() * qreal(-0.167) +
1851 prev.x() +
1852 point.x() * qreal(0.167),
1853 prevFar.y() * qreal(-0.167) +
1854 prev.y() +
1855 point.y() * qreal(0.167));
1856
1857 QPointF control2(prev.x() * qreal(0.167) +
1858 point.x() +
1859 next.x() * qreal(-0.167),
1860 prev.y() * qreal(0.167) +
1861 point.y() +
1862 next.y() * qreal(-0.167));
1863
1864 path.cubicTo(ctrlPt1: control1, ctrlPt2: control2, endPt: point);
1865}
1866
1867/****************************************************************************/
1868
1869/*!
1870 \qmltype PathArc
1871 \nativetype QQuickPathArc
1872 \inqmlmodule QtQuick
1873 \ingroup qtquick-animation-paths
1874 \brief Defines an arc with the given radius.
1875
1876 PathArc provides a simple way of specifying an arc that ends at a given position
1877 and uses the specified radius. It is modeled after the SVG elliptical arc command.
1878
1879 The following QML produces the path shown below:
1880 \table
1881 \row
1882 \li \image declarative-patharc.png
1883 \li \snippet qml/path/basicarc.qml 0
1884 \endtable
1885
1886 Note that a single PathArc cannot be used to specify a circle. Instead, you can
1887 use two PathArc elements, each specifying half of the circle.
1888
1889 \sa Path, PathLine, PathQuad, PathCubic, PathAngleArc, PathCurve, PathSvg
1890*/
1891
1892/*!
1893 \qmlproperty real QtQuick::PathArc::x
1894 \qmlproperty real QtQuick::PathArc::y
1895
1896 Defines the end point of the arc.
1897
1898 \sa relativeX, relativeY
1899*/
1900
1901/*!
1902 \qmlproperty real QtQuick::PathArc::relativeX
1903 \qmlproperty real QtQuick::PathArc::relativeY
1904
1905 Defines the end point of the arc relative to its start.
1906
1907 If both a relative and absolute end position are specified for a single axis, the relative
1908 position will be used.
1909
1910 Relative and absolute positions can be mixed, for example it is valid to set a relative x
1911 and an absolute y.
1912
1913 \sa x, y
1914*/
1915
1916/*!
1917 \qmlproperty real QtQuick::PathArc::radiusX
1918 \qmlproperty real QtQuick::PathArc::radiusY
1919
1920 Defines the radius of the arc.
1921
1922 The following QML demonstrates how different radius values can be used to change
1923 the shape of the arc:
1924 \table
1925 \row
1926 \li \image declarative-arcradius.png
1927 \li \snippet qml/path/arcradius.qml 0
1928 \endtable
1929*/
1930
1931qreal QQuickPathArc::radiusX() const
1932{
1933 return _radiusX;
1934}
1935
1936void QQuickPathArc::setRadiusX(qreal radius)
1937{
1938 if (_radiusX == radius)
1939 return;
1940
1941 _radiusX = radius;
1942 emit radiusXChanged();
1943 emit changed();
1944}
1945
1946qreal QQuickPathArc::radiusY() const
1947{
1948 return _radiusY;
1949}
1950
1951void QQuickPathArc::setRadiusY(qreal radius)
1952{
1953 if (_radiusY == radius)
1954 return;
1955
1956 _radiusY = radius;
1957 emit radiusYChanged();
1958 emit changed();
1959}
1960
1961/*!
1962 \qmlproperty bool QtQuick::PathArc::useLargeArc
1963 Whether to use a large arc as defined by the arc points.
1964
1965 Given fixed start and end positions, radius, and direction,
1966 there are two possible arcs that can fit the data. useLargeArc
1967 is used to distinguish between these. For example, the following
1968 QML can produce either of the two illustrated arcs below by
1969 changing the value of useLargeArc.
1970
1971 \table
1972 \row
1973 \li \image declarative-largearc.png
1974 \li \snippet qml/path/largearc.qml 0
1975 \endtable
1976
1977 The default value is false.
1978*/
1979
1980bool QQuickPathArc::useLargeArc() const
1981{
1982 return _useLargeArc;
1983}
1984
1985void QQuickPathArc::setUseLargeArc(bool largeArc)
1986{
1987 if (_useLargeArc == largeArc)
1988 return;
1989
1990 _useLargeArc = largeArc;
1991 emit useLargeArcChanged();
1992 emit changed();
1993}
1994
1995/*!
1996 \qmlproperty enumeration QtQuick::PathArc::direction
1997
1998 Defines the direction of the arc. Possible values are
1999 PathArc.Clockwise (default) and PathArc.Counterclockwise.
2000
2001 The following QML can produce either of the two illustrated arcs below
2002 by changing the value of direction.
2003 \table
2004 \row
2005 \li \image declarative-arcdirection.png
2006 \li \snippet qml/path/arcdirection.qml 0
2007 \endtable
2008
2009 \sa useLargeArc
2010*/
2011
2012QQuickPathArc::ArcDirection QQuickPathArc::direction() const
2013{
2014 return _direction;
2015}
2016
2017void QQuickPathArc::setDirection(ArcDirection direction)
2018{
2019 if (_direction == direction)
2020 return;
2021
2022 _direction = direction;
2023 emit directionChanged();
2024 emit changed();
2025}
2026
2027/*!
2028 \qmlproperty real QtQuick::PathArc::xAxisRotation
2029
2030 Defines the rotation of the arc, in degrees. The default value is 0.
2031
2032 An arc is a section of circles or ellipses. Given the radius and the start
2033 and end points, there are two ellipses that connect the points. This
2034 property defines the rotation of the X axis of these ellipses.
2035
2036 \note The value is only useful when the x and y radius differ, meaning the
2037 arc is a section of ellipses.
2038
2039 The following QML demonstrates how different radius values can be used to change
2040 the shape of the arc:
2041 \table
2042 \row
2043 \li \image declarative-arcrotation.png
2044 \li \snippet qml/path/arcrotation.qml 0
2045 \endtable
2046*/
2047
2048qreal QQuickPathArc::xAxisRotation() const
2049{
2050 return _xAxisRotation;
2051}
2052
2053void QQuickPathArc::setXAxisRotation(qreal rotation)
2054{
2055 if (_xAxisRotation == rotation)
2056 return;
2057
2058 _xAxisRotation = rotation;
2059 emit xAxisRotationChanged();
2060 emit changed();
2061}
2062
2063void QQuickPathArc::addToPath(QPainterPath &path, const QQuickPathData &data)
2064{
2065 const QPointF &startPoint = path.currentPosition();
2066 const QPointF &endPoint = positionForCurve(data, prevPoint: startPoint);
2067 QQuickSvgParser::pathArc(path,
2068 rx: _radiusX,
2069 ry: _radiusY,
2070 x_axis_rotation: _xAxisRotation,
2071 large_arc_flag: _useLargeArc,
2072 sweep_flag: _direction == Clockwise ? 1 : 0,
2073 x: endPoint.x(),
2074 y: endPoint.y(),
2075 curx: startPoint.x(), cury: startPoint.y());
2076}
2077
2078/****************************************************************************/
2079
2080/*!
2081 \qmltype PathAngleArc
2082 \nativetype QQuickPathAngleArc
2083 \inqmlmodule QtQuick
2084 \ingroup qtquick-animation-paths
2085 \brief Defines an arc with the given radii and center.
2086
2087 PathAngleArc provides a simple way of specifying an arc. While PathArc is designed
2088 to work as part of a larger path (specifying start and end), PathAngleArc is designed
2089 to make a path where the arc is primary (such as a circular progress indicator) more intuitive.
2090
2091 \sa Path, PathLine, PathQuad, PathCubic, PathCurve, PathSvg, PathArc, PathRectangle
2092*/
2093
2094/*!
2095 \qmlproperty real QtQuick::PathAngleArc::centerX
2096 \qmlproperty real QtQuick::PathAngleArc::centerY
2097
2098 Defines the center of the arc.
2099*/
2100
2101qreal QQuickPathAngleArc::centerX() const
2102{
2103 return _centerX;
2104}
2105
2106void QQuickPathAngleArc::setCenterX(qreal centerX)
2107{
2108 if (_centerX == centerX)
2109 return;
2110
2111 _centerX = centerX;
2112 emit centerXChanged();
2113 emit changed();
2114}
2115
2116qreal QQuickPathAngleArc::centerY() const
2117{
2118 return _centerY;
2119}
2120
2121void QQuickPathAngleArc::setCenterY(qreal centerY)
2122{
2123 if (_centerY == centerY)
2124 return;
2125
2126 _centerY = centerY;
2127 emit centerYChanged();
2128 emit changed();
2129}
2130
2131/*!
2132 \qmlproperty real QtQuick::PathAngleArc::radiusX
2133 \qmlproperty real QtQuick::PathAngleArc::radiusY
2134
2135 Defines the radii of the ellipse of which the arc is part.
2136*/
2137
2138qreal QQuickPathAngleArc::radiusX() const
2139{
2140 return _radiusX;
2141}
2142
2143void QQuickPathAngleArc::setRadiusX(qreal radius)
2144{
2145 if (_radiusX == radius)
2146 return;
2147
2148 _radiusX = radius;
2149 emit radiusXChanged();
2150 emit changed();
2151}
2152
2153qreal QQuickPathAngleArc::radiusY() const
2154{
2155 return _radiusY;
2156}
2157
2158void QQuickPathAngleArc::setRadiusY(qreal radius)
2159{
2160 if (_radiusY == radius)
2161 return;
2162
2163 _radiusY = radius;
2164 emit radiusYChanged();
2165 emit changed();
2166}
2167
2168/*!
2169 \qmlproperty real QtQuick::PathAngleArc::startAngle
2170
2171 Defines the start angle of the arc.
2172
2173 The start angle is reported clockwise, with zero degrees at the 3 o'clock position.
2174*/
2175
2176qreal QQuickPathAngleArc::startAngle() const
2177{
2178 return _startAngle;
2179}
2180
2181void QQuickPathAngleArc::setStartAngle(qreal angle)
2182{
2183 if (_startAngle == angle)
2184 return;
2185
2186 _startAngle = angle;
2187 emit startAngleChanged();
2188 emit changed();
2189}
2190
2191/*!
2192 \qmlproperty real QtQuick::PathAngleArc::sweepAngle
2193
2194 Defines the sweep angle of the arc.
2195
2196 The arc will begin at startAngle and continue sweepAngle degrees, with a value of 360
2197 resulting in a full circle. Positive numbers are clockwise and negative numbers are counterclockwise.
2198*/
2199
2200qreal QQuickPathAngleArc::sweepAngle() const
2201{
2202 return _sweepAngle;
2203}
2204
2205void QQuickPathAngleArc::setSweepAngle(qreal angle)
2206{
2207 if (_sweepAngle == angle)
2208 return;
2209
2210 _sweepAngle = angle;
2211 emit sweepAngleChanged();
2212 emit changed();
2213}
2214
2215/*!
2216 \qmlproperty bool QtQuick::PathAngleArc::moveToStart
2217
2218 Whether this element should be disconnected from the previous Path element (or startX/Y).
2219
2220 The default value is true. If set to false, the previous element's end-point
2221 (or startX/Y if PathAngleArc is the first element) will be connected to the arc's
2222 start-point with a straight line.
2223*/
2224
2225bool QQuickPathAngleArc::moveToStart() const
2226{
2227 return _moveToStart;
2228}
2229
2230void QQuickPathAngleArc::setMoveToStart(bool move)
2231{
2232 if (_moveToStart == move)
2233 return;
2234
2235 _moveToStart = move;
2236 emit moveToStartChanged();
2237 emit changed();
2238}
2239
2240void QQuickPathAngleArc::addToPath(QPainterPath &path, const QQuickPathData &)
2241{
2242 qreal x = _centerX - _radiusX;
2243 qreal y = _centerY - _radiusY;
2244 qreal width = _radiusX * 2;
2245 qreal height = _radiusY * 2;
2246 if (_moveToStart)
2247 path.arcMoveTo(x, y, w: width, h: height, angle: -_startAngle);
2248 path.arcTo(x, y, w: width, h: height, startAngle: -_startAngle, arcLength: -_sweepAngle);
2249}
2250
2251/****************************************************************************/
2252
2253/*!
2254 \qmltype PathSvg
2255 \nativetype QQuickPathSvg
2256 \inqmlmodule QtQuick
2257 \ingroup qtquick-animation-paths
2258 \brief Defines a path using an SVG path data string.
2259
2260 The following QML produces the path shown below:
2261 \table
2262 \row
2263 \li \image declarative-pathsvg.png
2264 \li
2265 \qml
2266 Path {
2267 startX: 50; startY: 50
2268 PathSvg { path: "L 150 50 L 100 150 z" }
2269 }
2270 \endqml
2271 \endtable
2272
2273 \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve
2274*/
2275
2276/*!
2277 \qmlproperty string QtQuick::PathSvg::path
2278
2279 The SVG path data string specifying the path.
2280
2281 See \l {http://www.w3.org/TR/SVG/paths.html#PathData}{W3C SVG Path Data}
2282 for more details on this format.
2283*/
2284
2285QString QQuickPathSvg::path() const
2286{
2287 return _path;
2288}
2289
2290void QQuickPathSvg::setPath(const QString &path)
2291{
2292 if (_path == path)
2293 return;
2294
2295 _path = path;
2296 emit pathChanged();
2297 emit changed();
2298}
2299
2300void QQuickPathSvg::addToPath(QPainterPath &path, const QQuickPathData &)
2301{
2302 QQuickSvgParser::parsePathDataFast(dataStr: _path, path);
2303}
2304
2305/****************************************************************************/
2306
2307/*!
2308 \qmltype PathRectangle
2309 \nativetype QQuickPathRectangle
2310 \inqmlmodule QtQuick
2311 \ingroup qtquick-animation-paths
2312 \brief Defines a rectangle with optionally rounded corners.
2313 \since QtQuick 6.8
2314
2315 PathRectangle provides an easy way to specify a rectangle, optionally with rounded corners. The
2316 API corresponds to that of the \l Rectangle item.
2317
2318 \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg
2319*/
2320
2321/*!
2322 \qmlproperty real QtQuick::PathRectangle::x
2323 \qmlproperty real QtQuick::PathRectangle::y
2324
2325 Defines the top left corner of the rectangle.
2326
2327 Unless that corner is rounded, this will also be the start and end point of the path.
2328
2329 \sa relativeX, relativeY
2330*/
2331
2332/*!
2333 \qmlproperty real QtQuick::PathRectangle::relativeX
2334 \qmlproperty real QtQuick::PathRectangle::relativeY
2335
2336 Defines the top left corner of the rectangle relative to the path's start point.
2337
2338 If both a relative and absolute end position are specified for a single axis, the relative
2339 position will be used.
2340
2341 Relative and absolute positions can be mixed, for example it is valid to set a relative x
2342 and an absolute y.
2343
2344 \sa x, y
2345*/
2346
2347/*!
2348 \qmlproperty real QtQuick::PathRectangle::width
2349 \qmlproperty real QtQuick::PathRectangle::height
2350
2351 Defines the width and height of the rectangle.
2352
2353 \sa x, y
2354*/
2355
2356qreal QQuickPathRectangle::width() const
2357{
2358 return _width;
2359}
2360
2361void QQuickPathRectangle::setWidth(qreal width)
2362{
2363 if (_width == width)
2364 return;
2365
2366 _width = width;
2367 emit widthChanged();
2368 emit changed();
2369}
2370
2371qreal QQuickPathRectangle::height() const
2372{
2373 return _height;
2374}
2375
2376void QQuickPathRectangle::setHeight(qreal height)
2377{
2378 if (_height == height)
2379 return;
2380
2381 _height = height;
2382 emit heightChanged();
2383 emit changed();
2384}
2385
2386/*!
2387 \qmlproperty real QtQuick::PathRectangle::strokeAdjustment
2388
2389 This property defines the stroke width adjustment to the rectangle coordinates.
2390
2391 When used in a \l ShapePath with stroking enabled, the actual stroked rectangle will by default
2392 extend beyond the defined rectangle by half the stroke width on all sides. This is the expected
2393 behavior since the path defines the midpoint line of the stroking, and corresponds to QPainter
2394 and SVG rendering.
2395
2396 If one instead wants the defined rectangle to be the outer edge of the stroked rectangle, like
2397 a \l Rectangle item with a border, one can set strokeAdjustment to the stroke width. This will
2398 effectively shift all edges inwards by half the stroke width. Like in the following example:
2399
2400 \qml
2401 ShapePath {
2402 id: myRec
2403 fillColor: "white"
2404 strokeColor: "black"
2405 strokeWidth: 16
2406 joinStyle: ShapePath.MiterJoin
2407
2408 PathRectangle { x: 10; y: 10; width: 200; height: 100; strokeAdjustment: myRec.strokeWidth }
2409 }
2410 \endqml
2411*/
2412
2413qreal QQuickPathRectangle::strokeAdjustment() const
2414{
2415 return _strokeAdjustment;
2416}
2417
2418void QQuickPathRectangle::setStrokeAdjustment(qreal newStrokeAdjustment)
2419{
2420 if (_strokeAdjustment == newStrokeAdjustment)
2421 return;
2422 _strokeAdjustment = newStrokeAdjustment;
2423 emit strokeAdjustmentChanged();
2424 emit changed();
2425}
2426
2427/*!
2428 \qmlproperty real QtQuick::PathRectangle::radius
2429
2430 This property defines the corner radius used to define a rounded rectangle.
2431
2432 If radius is a positive value, the rectangle path will be defined as a rounded rectangle,
2433 otherwise it will be defined as a normal rectangle.
2434
2435 This property may be overridden by the individual corner radius properties.
2436
2437 \sa topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius
2438*/
2439
2440qreal QQuickPathRectangle::radius() const
2441{
2442 return _extra.isAllocated() ? _extra->radius : 0;
2443}
2444
2445void QQuickPathRectangle::setRadius(qreal newRadius)
2446{
2447 if (_extra.value().radius == newRadius)
2448 return;
2449 _extra->radius = newRadius;
2450 emit radiusChanged();
2451 if (_extra->cornerRadii[Qt::TopLeftCorner] < 0)
2452 emit topLeftRadiusChanged();
2453 if (_extra->cornerRadii[Qt::TopRightCorner] < 0)
2454 emit topRightRadiusChanged();
2455 if (_extra->cornerRadii[Qt::BottomLeftCorner] < 0)
2456 emit bottomLeftRadiusChanged();
2457 if (_extra->cornerRadii[Qt::BottomRightCorner] < 0)
2458 emit bottomRightRadiusChanged();
2459 emit changed();
2460}
2461
2462/*!
2463 \qmlproperty real QtQuick::PathRectangle::topLeftRadius
2464 \qmlproperty real QtQuick::PathRectangle::topRightRadius
2465 \qmlproperty real QtQuick::PathRectangle::bottomLeftRadius
2466 \qmlproperty real QtQuick::PathRectangle::bottomRightRadius
2467
2468 If set, these properties define the individual corner radii. A zero value defines that corner
2469 to be sharp, while a positive value defines it to be rounded. When unset, the value of \l
2470 radius is used instead.
2471
2472 These properties are unset by default. Assign \c undefined to them to return them to the unset
2473 state.
2474
2475 \sa radius
2476*/
2477
2478qreal QQuickPathRectangle::cornerRadius(Qt::Corner corner) const
2479{
2480 if (_extra.isAllocated())
2481 return _extra->cornerRadii[corner] < 0 ? _extra->radius : _extra->cornerRadii[corner];
2482 else
2483 return 0;
2484}
2485
2486void QQuickPathRectangle::setCornerRadius(Qt::Corner corner, qreal newCornerRadius)
2487{
2488 if (newCornerRadius < 0 || _extra.value().cornerRadii[corner] == newCornerRadius)
2489 return;
2490 _extra->cornerRadii[corner] = newCornerRadius;
2491 emitCornerRadiusChanged(corner);
2492}
2493
2494void QQuickPathRectangle::resetCornerRadius(Qt::Corner corner)
2495{
2496 if (!_extra.isAllocated() || _extra->cornerRadii[corner] < 0)
2497 return;
2498 _extra->cornerRadii[corner] = -1;
2499 emitCornerRadiusChanged(corner);
2500}
2501
2502void QQuickPathRectangle::emitCornerRadiusChanged(Qt::Corner corner)
2503{
2504 switch (corner) {
2505 case Qt::TopLeftCorner:
2506 emit topLeftRadiusChanged();
2507 break;
2508 case Qt::TopRightCorner:
2509 emit topRightRadiusChanged();
2510 break;
2511 case Qt::BottomLeftCorner:
2512 emit bottomLeftRadiusChanged();
2513 break;
2514 case Qt::BottomRightCorner:
2515 emit bottomRightRadiusChanged();
2516 break;
2517 }
2518 emit changed();
2519}
2520
2521void QQuickPathRectangle::addToPath(QPainterPath &path, const QQuickPathData &data)
2522{
2523 QRectF rect(positionForCurve(data, prevPoint: path.currentPosition()), QSizeF(_width, _height));
2524
2525 qreal halfStroke = _strokeAdjustment * 0.5;
2526 rect.adjust(xp1: halfStroke, yp1: halfStroke, xp2: -halfStroke, yp2: -halfStroke);
2527 if (rect.isEmpty())
2528 return;
2529
2530 if (!_extra.isAllocated()) {
2531 // No rounded corners
2532 path.addRect(rect);
2533 } else {
2534 // Radii must not exceed half of the width or half of the height
2535 const qreal maxDiameter = qMin(a: rect.width(), b: rect.height());
2536 const qreal generalDiameter = qMax(a: qreal(0), b: qMin(a: maxDiameter, b: 2 * _extra->radius));
2537 auto effectiveDiameter = [&](Qt::Corner corner) {
2538 qreal radius = _extra->cornerRadii[corner];
2539 return radius < 0 ? generalDiameter : qMin(a: maxDiameter, b: 2 * radius);
2540 };
2541 const qreal diamTL = effectiveDiameter(Qt::TopLeftCorner);
2542 const qreal diamTR = effectiveDiameter(Qt::TopRightCorner);
2543 const qreal diamBL = effectiveDiameter(Qt::BottomLeftCorner);
2544 const qreal diamBR = effectiveDiameter(Qt::BottomRightCorner);
2545
2546 path.moveTo(x: rect.left() + diamTL * 0.5, y: rect.top());
2547 if (diamTR)
2548 path.arcTo(rect: QRectF(QPointF(rect.right() - diamTR, rect.top()), QSizeF(diamTR, diamTR)), startAngle: 90, arcLength: -90);
2549 else
2550 path.lineTo(p: rect.topRight());
2551 if (diamBR)
2552 path.arcTo(rect: QRectF(QPointF(rect.right() - diamBR, rect.bottom() - diamBR), QSizeF(diamBR, diamBR)), startAngle: 0, arcLength: -90);
2553 else
2554 path.lineTo(p: rect.bottomRight());
2555 if (diamBL)
2556 path.arcTo(rect: QRectF(QPointF(rect.left(), rect.bottom() - diamBL), QSizeF(diamBL, diamBL)), startAngle: 270, arcLength: -90);
2557 else
2558 path.lineTo(p: rect.bottomLeft());
2559 if (diamTL)
2560 path.arcTo(rect: QRectF(rect.topLeft(), QSizeF(diamTL, diamTL)), startAngle: 180, arcLength: -90);
2561 else
2562 path.lineTo(p: rect.topLeft());
2563 path.closeSubpath();
2564 }
2565}
2566
2567/****************************************************************************/
2568
2569/*!
2570 \qmltype PathPercent
2571 \nativetype QQuickPathPercent
2572 \inqmlmodule QtQuick
2573 \ingroup qtquick-animation-paths
2574 \brief Manipulates the way a path is interpreted.
2575
2576 PathPercent allows you to manipulate the spacing between items on a
2577 PathView's path. You can use it to bunch together items on part of
2578 the path, and spread them out on other parts of the path.
2579
2580 The examples below show the normal distribution of items along a path
2581 compared to a distribution which places 50% of the items along the
2582 PathLine section of the path.
2583 \table
2584 \row
2585 \li \image declarative-nopercent.png
2586 \li
2587 \qml
2588 PathView {
2589 // ...
2590 Path {
2591 startX: 20; startY: 0
2592 PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
2593 PathLine { x: 150; y: 80 }
2594 PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 }
2595 }
2596 }
2597 \endqml
2598 \row
2599 \li \image declarative-percent.png
2600 \li
2601 \qml
2602 PathView {
2603 // ...
2604 Path {
2605 startX: 20; startY: 0
2606 PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 }
2607 PathPercent { value: 0.25 }
2608 PathLine { x: 150; y: 80 }
2609 PathPercent { value: 0.75 }
2610 PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 }
2611 PathPercent { value: 1 }
2612 }
2613 }
2614 \endqml
2615 \endtable
2616
2617 \sa Path
2618*/
2619
2620/*!
2621 \qmlproperty real QtQuick::PathPercent::value
2622 The proportion of items that should be laid out up to this point.
2623
2624 This value should always be higher than the last value specified
2625 by a PathPercent at a previous position in the Path.
2626
2627 In the following example we have a Path made up of three PathLines.
2628 Normally, the items of the PathView would be laid out equally along
2629 this path, with an equal number of items per line segment. PathPercent
2630 allows us to specify that the first and third lines should each hold
2631 10% of the laid out items, while the second line should hold the remaining
2632 80%.
2633
2634 \qml
2635 PathView {
2636 // ...
2637 Path {
2638 startX: 0; startY: 0
2639 PathLine { x:100; y: 0; }
2640 PathPercent { value: 0.1 }
2641 PathLine { x: 100; y: 100 }
2642 PathPercent { value: 0.9 }
2643 PathLine { x: 100; y: 0 }
2644 PathPercent { value: 1 }
2645 }
2646 }
2647 \endqml
2648*/
2649
2650qreal QQuickPathPercent::value() const
2651{
2652 return _value;
2653}
2654
2655void QQuickPathPercent::setValue(qreal value)
2656{
2657 if (_value != value) {
2658 _value = value;
2659 emit valueChanged();
2660 emit changed();
2661 }
2662}
2663
2664/*!
2665 \qmltype PathPolyline
2666 \nativetype QQuickPathPolyline
2667 \inqmlmodule QtQuick
2668 \ingroup qtquick-animation-paths
2669 \brief Defines a polyline through a list of coordinates.
2670 \since QtQuick 2.14
2671
2672 The example below creates a triangular path consisting of four vertices
2673 on the edge of the containing Shape's bounding box.
2674 Through the containing shape's \l {QtQuick::Path::}{scale} property,
2675 the path will be rescaled together with its containing shape.
2676
2677 \qml
2678 PathPolyline {
2679 id: ppl
2680 path: [ Qt.point(0.0, 0.0),
2681 Qt.point(1.0, 0.0),
2682 Qt.point(0.5, 1.0),
2683 Qt.point(0.0, 0.0)
2684 ]
2685 }
2686 \endqml
2687
2688 \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline
2689*/
2690
2691/*!
2692 \qmlproperty point QtQuick::PathPolyline::start
2693
2694 This read-only property contains the beginning of the polyline.
2695*/
2696
2697/*!
2698 \qmlproperty list<point> QtQuick::PathPolyline::path
2699
2700 This property defines the vertices of the polyline.
2701
2702 It can be a JS array of points constructed with \c Qt.point(),
2703 a QList or QVector of QPointF, or QPolygonF.
2704 If you are binding this to a custom property in some C++ object,
2705 QPolygonF is the most appropriate type to use.
2706*/
2707
2708QQuickPathPolyline::QQuickPathPolyline(QObject *parent) : QQuickCurve(parent)
2709{
2710}
2711
2712QVariant QQuickPathPolyline::path() const
2713{
2714 return QVariant::fromValue(value: m_path);
2715}
2716
2717void QQuickPathPolyline::setPath(const QVariant &path)
2718{
2719 if (path.userType() == QMetaType::QPolygonF) {
2720 setPath(path.value<QPolygonF>());
2721 } else if (path.canConvert<QVector<QPointF>>()) {
2722 setPath(path.value<QVector<QPointF>>());
2723 } else if (path.canConvert<QVariantList>()) {
2724 // This handles cases other than QPolygonF or QVector<QPointF>, such as
2725 // QList<QPointF>, QVector<QPoint>, QVariantList of QPointF, QVariantList of QPoint.
2726 QVector<QPointF> pathList;
2727 QVariantList vl = path.value<QVariantList>();
2728 // If path is a QJSValue, e.g. coming from a JS array of Qt.point() in QML,
2729 // then path.value<QVariantList>() is inefficient.
2730 // TODO We should be able to iterate over path.value<QSequentialIterable>() eventually
2731 for (const QVariant &v : vl)
2732 pathList.append(t: v.toPointF());
2733 setPath(pathList);
2734 } else {
2735 qWarning() << "PathPolyline: path of type" << path.userType() << "not supported";
2736 }
2737}
2738
2739void QQuickPathPolyline::setPath(const QVector<QPointF> &path)
2740{
2741 if (m_path != path) {
2742 const QPointF &oldStart = start();
2743 m_path = path;
2744 const QPointF &newStart = start();
2745 emit pathChanged();
2746 if (oldStart != newStart)
2747 emit startChanged();
2748 emit changed();
2749 }
2750}
2751
2752QPointF QQuickPathPolyline::start() const
2753{
2754 if (m_path.size()) {
2755 const QPointF &p = m_path.first();
2756 return p;
2757 }
2758 return QPointF();
2759}
2760
2761void QQuickPathPolyline::addToPath(QPainterPath &path, const QQuickPathData &/*data*/)
2762{
2763 if (m_path.size() < 2)
2764 return;
2765
2766 path.moveTo(p: m_path.first());
2767 for (int i = 1; i < m_path.size(); ++i)
2768 path.lineTo(p: m_path.at(i));
2769}
2770
2771
2772/*!
2773 \qmltype PathMultiline
2774 \nativetype QQuickPathMultiline
2775 \inqmlmodule QtQuick
2776 \ingroup qtquick-animation-paths
2777 \brief Defines a set of polylines through a list of lists of coordinates.
2778 \since QtQuick 2.14
2779
2780 This element allows to define a list of polylines at once.
2781 Each polyline in the list will be preceded by a \l{QPainterPath::moveTo}{moveTo}
2782 command, effectively making each polyline a separate one.
2783 The polylines in this list are supposed to be non-intersecting with each other.
2784 In any case, when used in conjunction with a \l ShapePath, the containing ShapePath's
2785 \l ShapePath::fillRule applies.
2786 That is, with the default \c OddEvenFill and non intersecting shapes, the largest shape in the list defines an area to be filled;
2787 areas where two shapes overlap are holes; areas where three shapes overlap are filled areas inside holes, etc.
2788
2789 The example below creates a high voltage symbol by adding each path
2790 of the symbol to the list of paths.
2791 The coordinates of the vertices are normalized, and through the containing shape's
2792 \l {QtQuick::Path::}{scale} property, the path will be rescaled together with its containing shape.
2793
2794 \qml
2795 PathMultiline {
2796 paths: [
2797 [Qt.point(0.5, 0.06698),
2798 Qt.point(1, 0.93301),
2799 Qt.point(0, 0.93301),
2800 Qt.point(0.5, 0.06698)],
2801
2802 [Qt.point(0.5, 0.12472),
2803 Qt.point(0.95, 0.90414),
2804 Qt.point(0.05, 0.90414),
2805 Qt.point(0.5, 0.12472)],
2806
2807 [Qt.point(0.47131, 0.32986),
2808 Qt.point(0.36229, 0.64789),
2809 Qt.point(0.51492, 0.58590),
2810 Qt.point(0.47563, 0.76014),
2811 Qt.point(0.44950, 0.73590),
2812 Qt.point(0.46292, 0.83392),
2813 Qt.point(0.52162, 0.75190),
2814 Qt.point(0.48531, 0.76230),
2815 Qt.point(0.57529, 0.53189),
2816 Qt.point(0.41261, 0.59189),
2817 Qt.point(0.53001, 0.32786),
2818 Qt.point(0.47131, 0.32986)]
2819 ]
2820 }
2821 \endqml
2822
2823 \sa Path, QPainterPath::setFillRule, PathPolyline, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove
2824*/
2825
2826/*!
2827 \qmlproperty point QtQuick::PathMultiline::start
2828
2829 This read-only property contains the beginning of the polylines.
2830*/
2831
2832/*!
2833 \qmlproperty list<list<point>> QtQuick::PathMultiline::paths
2834
2835 This property defines the vertices of the polylines.
2836
2837 It can be a JS array of JS arrays of points constructed with \c Qt.point(),
2838 a QList or QVector of QPolygonF, or QVector<QVector<QPointF>>.
2839 If you are binding this to a custom property in some C++ object,
2840 QVector<QPolygonF> or QVector<QVector<QPointF>> is the most
2841 appropriate type to use.
2842*/
2843
2844QQuickPathMultiline::QQuickPathMultiline(QObject *parent) : QQuickCurve(parent)
2845{
2846}
2847
2848QVariant QQuickPathMultiline::paths() const
2849{
2850 return QVariant::fromValue(value: m_paths);
2851}
2852
2853void QQuickPathMultiline::setPaths(const QVariant &paths)
2854{
2855 if (paths.canConvert<QVector<QPolygonF>>()) {
2856 const QVector<QPolygonF> pathPolygons = paths.value<QVector<QPolygonF>>();
2857 QVector<QVector<QPointF>> pathVectors;
2858 for (const QPolygonF &p : pathPolygons)
2859 pathVectors << p;
2860 setPaths(pathVectors);
2861 } else if (paths.canConvert<QVector<QVector<QPointF>>>()) {
2862 setPaths(paths.value<QVector<QVector<QPointF>>>());
2863 } else if (paths.canConvert<QVariantList>()) {
2864 // This handles cases other than QVector<QPolygonF> or QVector<QVector<QPointF>>, such as
2865 // QList<QVector<QPointF>>, QList<QList<QPointF>>, QVariantList of QVector<QPointF>,
2866 // QVariantList of QVariantList of QPointF, QVector<QList<QPoint>> etc.
2867 QVector<QVector<QPointF>> pathsList;
2868 QVariantList vll = paths.value<QVariantList>();
2869 for (const QVariant &v : vll) {
2870 // If we bind a QVector<QPolygonF> property directly, rather than via QVariant,
2871 // it will come through as QJSValue that can be converted to QVariantList of QPolygonF.
2872 if (v.canConvert<QPolygonF>()) {
2873 pathsList.append(t: v.value<QPolygonF>());
2874 } else {
2875 QVariantList vl = v.value<QVariantList>();
2876 QVector<QPointF> l;
2877 for (const QVariant &point : vl) {
2878 if (point.canConvert<QPointF>())
2879 l.append(t: point.toPointF());
2880 }
2881 if (l.size() >= 2)
2882 pathsList.append(t: l);
2883 }
2884 }
2885 setPaths(pathsList);
2886 } else {
2887 qWarning() << "PathMultiline: paths of type" << paths.userType() << "not supported";
2888 setPaths(QVector<QVector<QPointF>>());
2889 }
2890}
2891
2892void QQuickPathMultiline::setPaths(const QVector<QVector<QPointF>> &paths)
2893{
2894 if (m_paths != paths) {
2895 const QPointF &oldStart = start();
2896 m_paths = paths;
2897 const QPointF &newStart = start();
2898 emit pathsChanged();
2899 if (oldStart != newStart)
2900 emit startChanged();
2901 emit changed();
2902 }
2903}
2904
2905QPointF QQuickPathMultiline::start() const
2906{
2907 if (m_paths.size())
2908 return m_paths.first().first();
2909 return QPointF();
2910}
2911
2912void QQuickPathMultiline::addToPath(QPainterPath &path, const QQuickPathData &)
2913{
2914 if (!m_paths.size())
2915 return;
2916 for (const QVector<QPointF> &p: m_paths) {
2917 path.moveTo(p: p.first());
2918 for (int i = 1; i < p.size(); ++i)
2919 path.lineTo(p: p.at(i));
2920 }
2921}
2922
2923/*!
2924 \qmltype PathText
2925 \nativetype QQuickPathText
2926 \inqmlmodule QtQuick
2927 \ingroup qtquick-animation-paths
2928 \brief Defines a string in a specified font.
2929 \since QtQuick 2.15
2930
2931 This element defines the shape of a specified string in a specified font. The text's
2932 baseline will be translated to the x and y coordinates, and the outlines from the font
2933 will be added to the path accordingly.
2934
2935 \qml
2936 PathText {
2937 x: 0
2938 y: font.pixelSize
2939 font.family: "Arial"
2940 font.pixelSize: 100
2941 text: "Foobar"
2942 }
2943 \endqml
2944
2945 \sa Path, QPainterPath::setFillRule, PathPolyline, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove
2946*/
2947
2948/*!
2949 \qmlproperty real QtQuick::PathText::x
2950
2951 The horizontal position of the PathText's baseline.
2952*/
2953
2954/*!
2955 \qmlproperty real QtQuick::PathText::y
2956
2957 The vertical position of the PathText's baseline.
2958
2959 \note This property refers to the position of the baseline of the text, not the top of its bounding box. This may
2960 cause some confusion, e.g. when using the PathText with Qt Quick Shapes. See \l FontMetrics for information on how to
2961 get the ascent of a font, which can be used to translate the text into the expected position.
2962*/
2963
2964/*!
2965 \qmlproperty string QtQuick::PathText::text
2966
2967 The text for which this PathText should contain the outlines.
2968*/
2969
2970/*!
2971 \qmlproperty string QtQuick::PathText::font.family
2972
2973 Sets the family name of the font.
2974
2975 The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
2976 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
2977 If the family isn't available a family will be set using the font matching algorithm.
2978*/
2979
2980/*!
2981 \qmlproperty string QtQuick::PathText::font.styleName
2982
2983 Sets the style name of the font.
2984
2985 The style name is case insensitive. If set, the font will be matched against style name instead
2986 of the font properties \l font.weight, \l font.bold and \l font.italic.
2987*/
2988
2989/*!
2990 \qmlproperty bool QtQuick::PathText::font.bold
2991
2992 Sets whether the font weight is bold.
2993*/
2994
2995/*!
2996 \qmlproperty int QtQuick::PathText::font.weight
2997
2998 Sets the font's weight.
2999
3000 The weight can be one of:
3001
3002 \value Font.Thin 100
3003 \value Font.ExtraLight 200
3004 \value Font.Light 300
3005 \value Font.Normal 400 (default)
3006 \value Font.Medium 500
3007 \value Font.DemiBold 600
3008 \value Font.Bold 700
3009 \value Font.ExtraBold 800
3010 \value Font.Black 900
3011
3012 \qml
3013 PathText { text: "Hello"; font.weight: Font.DemiBold }
3014 \endqml
3015*/
3016
3017/*!
3018 \qmlproperty bool QtQuick::PathText::font.italic
3019
3020 Sets whether the font has an italic style.
3021*/
3022
3023/*!
3024 \qmlproperty bool QtQuick::PathText::font.underline
3025
3026 Sets whether the text is underlined.
3027*/
3028
3029/*!
3030 \qmlproperty bool QtQuick::PathText::font.strikeout
3031
3032 Sets whether the font has a strikeout style.
3033*/
3034
3035/*!
3036 \qmlproperty real QtQuick::PathText::font.pointSize
3037
3038 Sets the font size in points. The point size must be greater than zero.
3039*/
3040
3041/*!
3042 \qmlproperty int QtQuick::PathText::font.pixelSize
3043
3044 Sets the font size in pixels.
3045
3046 Using this function makes the font device dependent.
3047 Use \c pointSize to set the size of the font in a device independent manner.
3048*/
3049
3050/*!
3051 \qmlproperty real QtQuick::PathText::font.letterSpacing
3052
3053 Sets the letter spacing for the font.
3054
3055 Letter spacing changes the default spacing between individual letters in the font.
3056 A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
3057*/
3058
3059/*!
3060 \qmlproperty real QtQuick::PathText::font.wordSpacing
3061
3062 Sets the word spacing for the font.
3063
3064 Word spacing changes the default spacing between individual words.
3065 A positive value increases the word spacing by a corresponding amount of pixels,
3066 while a negative value decreases the inter-word spacing accordingly.
3067*/
3068
3069/*!
3070 \qmlproperty enumeration QtQuick::PathText::font.capitalization
3071
3072 Sets the capitalization for the text.
3073
3074 \value Font.MixedCase no capitalization change is applied
3075 \value Font.AllUppercase alters the text to be rendered in all uppercase type
3076 \value Font.AllLowercase alters the text to be rendered in all lowercase type
3077 \value Font.SmallCaps alters the text to be rendered in small-caps type
3078 \value Font.Capitalize alters the text to be rendered with the first character of
3079 each word as an uppercase character
3080
3081 \qml
3082 PathText { text: "Hello"; font.capitalization: Font.AllLowercase }
3083 \endqml
3084*/
3085
3086/*!
3087 \qmlproperty bool QtQuick::PathText::font.kerning
3088
3089 Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
3090 improve performance when creating or changing the text, at the expense of some cosmetic
3091 features. The default value is true.
3092
3093 \qml
3094 PathText { text: "OATS FLAVOUR WAY"; font.kerning: false }
3095 \endqml
3096*/
3097
3098/*!
3099 \qmlproperty bool QtQuick::PathText::font.preferShaping
3100
3101 Sometimes, a font will apply complex rules to a set of characters in order to
3102 display them correctly. In some writing systems, such as Brahmic scripts, this is
3103 required in order for the text to be legible, but in e.g. Latin script, it is merely
3104 a cosmetic feature. Setting the \c preferShaping property to false will disable all
3105 such features when they are not required, which will improve performance in most cases.
3106
3107 The default value is true.
3108
3109 \qml
3110 PathText { text: "Some text"; font.preferShaping: false }
3111 \endqml
3112*/
3113
3114/*!
3115 \qmlproperty object QtQuick::PathText::font.variableAxes
3116 \since 6.7
3117
3118 \include qquicktext.cpp qml-font-variable-axes
3119*/
3120
3121/*!
3122 \qmlproperty object QtQuick::PathText::font.features
3123 \since 6.6
3124
3125 \include qquicktext.cpp qml-font-features
3126*/
3127
3128/*!
3129 \qmlproperty bool QtQuick::PathText::font.contextFontMerging
3130 \since 6.8
3131
3132 \include qquicktext.cpp qml-font-context-font-merging
3133*/
3134
3135/*!
3136 \qmlproperty bool QtQuick::PathText::font.preferTypoLineMetrics
3137 \since 6.8
3138
3139 \include qquicktext.cpp qml-font-prefer-typo-line-metrics
3140*/
3141void QQuickPathText::updatePath() const
3142{
3143 if (!_path.isEmpty())
3144 return;
3145
3146 _path.addText(x: 0.0, y: 0.0, f: _font, text: _text);
3147
3148 // Account for distance from baseline to top, since addText() takes baseline position
3149 QRectF brect = _path.boundingRect();
3150 _path.translate(dx: _x, dy: _y - brect.y());
3151}
3152
3153void QQuickPathText::addToPath(QPainterPath &path)
3154{
3155 if (_text.isEmpty())
3156 return;
3157 updatePath();
3158 path.addPath(path: _path);
3159}
3160
3161QT_END_NAMESPACE
3162
3163#include "moc_qquickpath_p.cpp"
3164

source code of qtdeclarative/src/quick/util/qquickpath.cpp