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

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