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

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