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 "qquickitemanimation_p.h"
41#include "qquickitemanimation_p_p.h"
42#include "qquickstateoperations_p.h"
43
44#include <private/qqmlproperty_p.h>
45#if QT_CONFIG(quick_path)
46#include <private/qquickpath_p.h>
47#endif
48#include "private/qparallelanimationgroupjob_p.h"
49#include "private/qsequentialanimationgroupjob_p.h"
50
51#include <QtCore/qmath.h>
52#include <QtGui/qtransform.h>
53#include <QtQml/qqmlinfo.h>
54
55QT_BEGIN_NAMESPACE
56
57/*!
58 \qmltype ParentAnimation
59 \instantiates QQuickParentAnimation
60 \inqmlmodule QtQuick
61 \ingroup qtquick-animation-properties
62 \since 5.0
63 \inherits Animation
64 \brief Animates changes in parent values.
65
66 ParentAnimation is used to animate a parent change for an \l Item.
67
68 For example, the following ParentChange changes \c blueRect to become
69 a child of \c redRect when it is clicked. The inclusion of the
70 ParentAnimation, which defines a NumberAnimation to be applied during
71 the transition, ensures the item animates smoothly as it moves to
72 its new parent:
73
74 \snippet qml/parentanimation.qml 0
75
76 A ParentAnimation can contain any number of animations. These animations will
77 be run in parallel; to run them sequentially, define them within a
78 SequentialAnimation.
79
80 In some cases, such as when reparenting between items with clipping enabled, it is useful
81 to animate the parent change via another item that does not have clipping
82 enabled. Such an item can be set using the \l via property.
83
84 ParentAnimation is typically used within a \l Transition in conjunction
85 with a ParentChange. When used in this manner, it animates any
86 ParentChange that has occurred during the state change. This can be
87 overridden by setting a specific target item using the \l target property.
88
89 \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation}
90*/
91QQuickParentAnimation::QQuickParentAnimation(QObject *parent)
92 : QQuickAnimationGroup(*(new QQuickParentAnimationPrivate), parent)
93{
94}
95
96QQuickParentAnimation::~QQuickParentAnimation()
97{
98}
99
100/*!
101 \qmlproperty Item QtQuick::ParentAnimation::target
102 The item to reparent.
103
104 When used in a transition, if no target is specified, all
105 ParentChange occurrences are animated by the ParentAnimation.
106*/
107QQuickItem *QQuickParentAnimation::target() const
108{
109 Q_D(const QQuickParentAnimation);
110 return d->target;
111}
112
113void QQuickParentAnimation::setTargetObject(QQuickItem *target)
114{
115 Q_D(QQuickParentAnimation);
116 if (target == d->target)
117 return;
118
119 d->target = target;
120 emit targetChanged();
121}
122
123/*!
124 \qmlproperty Item QtQuick::ParentAnimation::newParent
125 The new parent to animate to.
126
127 If the ParentAnimation is defined within a \l Transition,
128 this value defaults to the value defined in the end state of the
129 \l Transition.
130*/
131QQuickItem *QQuickParentAnimation::newParent() const
132{
133 Q_D(const QQuickParentAnimation);
134 return d->newParent;
135}
136
137void QQuickParentAnimation::setNewParent(QQuickItem *newParent)
138{
139 Q_D(QQuickParentAnimation);
140 if (newParent == d->newParent)
141 return;
142
143 d->newParent = newParent;
144 emit newParentChanged();
145}
146
147/*!
148 \qmlproperty Item QtQuick::ParentAnimation::via
149 The item to reparent via. This provides a way to do an unclipped animation
150 when both the old parent and new parent are clipped.
151
152 \qml
153 ParentAnimation {
154 target: myItem
155 via: topLevelItem
156 // ...
157 }
158 \endqml
159
160 \note This only works when the ParentAnimation is used in a \l Transition
161 in conjunction with a ParentChange.
162*/
163QQuickItem *QQuickParentAnimation::via() const
164{
165 Q_D(const QQuickParentAnimation);
166 return d->via;
167}
168
169void QQuickParentAnimation::setVia(QQuickItem *via)
170{
171 Q_D(QQuickParentAnimation);
172 if (via == d->via)
173 return;
174
175 d->via = via;
176 emit viaChanged();
177}
178
179//### mirrors same-named function in QQuickItem
180QPointF QQuickParentAnimationPrivate::computeTransformOrigin(QQuickItem::TransformOrigin origin, qreal width, qreal height) const
181{
182 switch (origin) {
183 default:
184 case QQuickItem::TopLeft:
185 return QPointF(0, 0);
186 case QQuickItem::Top:
187 return QPointF(width / 2., 0);
188 case QQuickItem::TopRight:
189 return QPointF(width, 0);
190 case QQuickItem::Left:
191 return QPointF(0, height / 2.);
192 case QQuickItem::Center:
193 return QPointF(width / 2., height / 2.);
194 case QQuickItem::Right:
195 return QPointF(width, height / 2.);
196 case QQuickItem::BottomLeft:
197 return QPointF(0, height);
198 case QQuickItem::Bottom:
199 return QPointF(width / 2., height);
200 case QQuickItem::BottomRight:
201 return QPointF(width, height);
202 }
203}
204
205struct QQuickParentAnimationData : public QAbstractAnimationAction
206{
207 QQuickParentAnimationData() : reverse(false) {}
208 ~QQuickParentAnimationData() { qDeleteAll(c: pc); }
209
210 QQuickStateActions actions;
211 //### reverse should probably apply on a per-action basis
212 bool reverse;
213 QList<QQuickParentChange *> pc;
214 void doAction() override
215 {
216 for (int ii = 0; ii < actions.count(); ++ii) {
217 const QQuickStateAction &action = actions.at(i: ii);
218 if (reverse)
219 action.event->reverse();
220 else
221 action.event->execute();
222 }
223 }
224};
225
226QAbstractAnimationJob* QQuickParentAnimation::transition(QQuickStateActions &actions,
227 QQmlProperties &modified,
228 TransitionDirection direction,
229 QObject *defaultTarget)
230{
231 Q_D(QQuickParentAnimation);
232
233 std::unique_ptr<QQuickParentAnimationData> data(new QQuickParentAnimationData);
234 std::unique_ptr<QQuickParentAnimationData> viaData(new QQuickParentAnimationData);
235
236 bool hasExplicit = false;
237 if (d->target && d->newParent) {
238 data->reverse = false;
239 QQuickStateAction myAction;
240 QQuickParentChange *pc = new QQuickParentChange;
241 pc->setObject(d->target);
242 pc->setParent(d->newParent);
243 myAction.event = pc;
244 data->pc << pc;
245 data->actions << myAction;
246 hasExplicit = true;
247 if (d->via) {
248 viaData->reverse = false;
249 QQuickStateAction myVAction;
250 QQuickParentChange *vpc = new QQuickParentChange;
251 vpc->setObject(d->target);
252 vpc->setParent(d->via);
253 myVAction.event = vpc;
254 viaData->pc << vpc;
255 viaData->actions << myVAction;
256 }
257 //### once actions have concept of modified,
258 // loop to match appropriate ParentChanges and mark as modified
259 }
260
261 if (!hasExplicit)
262 for (int i = 0; i < actions.size(); ++i) {
263 QQuickStateAction &action = actions[i];
264 if (action.event && action.event->type() == QQuickStateActionEvent::ParentChange
265 && (!d->target || static_cast<QQuickParentChange*>(action.event)->object() == d->target)) {
266
267 QQuickParentChange *pc = static_cast<QQuickParentChange*>(action.event);
268 QQuickStateAction myAction = action;
269 data->reverse = action.reverseEvent;
270
271 //### this logic differs from PropertyAnimation
272 // (probably a result of modified vs. done)
273 if (d->newParent) {
274 QQuickParentChange *epc = new QQuickParentChange;
275 epc->setObject(static_cast<QQuickParentChange*>(action.event)->object());
276 epc->setParent(d->newParent);
277 myAction.event = epc;
278 data->pc << epc;
279 data->actions << myAction;
280 pc = epc;
281 } else {
282 action.actionDone = true;
283 data->actions << myAction;
284 }
285
286 if (d->via) {
287 viaData->reverse = false;
288 QQuickStateAction myAction;
289 QQuickParentChange *vpc = new QQuickParentChange;
290 vpc->setObject(pc->object());
291 vpc->setParent(d->via);
292 myAction.event = vpc;
293 viaData->pc << vpc;
294 viaData->actions << myAction;
295 QQuickStateAction dummyAction;
296 QQuickStateAction &xAction = pc->xIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
297 QQuickStateAction &yAction = pc->yIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
298 QQuickStateAction &sAction = pc->scaleIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
299 QQuickStateAction &rAction = pc->rotationIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
300 QQuickItem *target = pc->object();
301 QQuickItem *targetParent = action.reverseEvent ? pc->originalParent() : pc->parent();
302
303 //### this mirrors the logic in QQuickParentChange.
304 bool ok;
305 const QTransform &transform = targetParent->itemTransform(d->via, &ok);
306 if (transform.type() >= QTransform::TxShear || !ok) {
307 qmlWarning(me: this) << QQuickParentAnimation::tr(s: "Unable to preserve appearance under complex transform");
308 ok = false;
309 }
310
311 qreal scale = 1;
312 qreal rotation = 0;
313 bool isRotate = (transform.type() == QTransform::TxRotate) || (transform.m11() < 0);
314 if (ok && !isRotate) {
315 if (transform.m11() == transform.m22())
316 scale = transform.m11();
317 else {
318 qmlWarning(me: this) << QQuickParentAnimation::tr(s: "Unable to preserve appearance under non-uniform scale");
319 ok = false;
320 }
321 } else if (ok && isRotate) {
322 if (transform.m11() == transform.m22())
323 scale = qSqrt(v: transform.m11()*transform.m11() + transform.m12()*transform.m12());
324 else {
325 qmlWarning(me: this) << QQuickParentAnimation::tr(s: "Unable to preserve appearance under non-uniform scale");
326 ok = false;
327 }
328
329 if (scale != 0)
330 rotation = qRadiansToDegrees(radians: qAtan2(y: transform.m12() / scale, x: transform.m11() / scale));
331 else {
332 qmlWarning(me: this) << QQuickParentAnimation::tr(s: "Unable to preserve appearance under scale of 0");
333 ok = false;
334 }
335 }
336
337 const QPointF &point = transform.map(p: QPointF(xAction.toValue.toReal(),yAction.toValue.toReal()));
338 qreal x = point.x();
339 qreal y = point.y();
340 if (ok && target->transformOrigin() != QQuickItem::TopLeft) {
341 qreal w = target->width();
342 qreal h = target->height();
343 if (pc->widthIsSet() && i < actions.size() - 1)
344 w = actions.at(i: ++i).toValue.toReal();
345 if (pc->heightIsSet() && i < actions.size() - 1)
346 h = actions.at(i: ++i).toValue.toReal();
347 const QPointF &transformOrigin
348 = d->computeTransformOrigin(origin: target->transformOrigin(), width: w,height: h);
349 qreal tempxt = transformOrigin.x();
350 qreal tempyt = transformOrigin.y();
351 QTransform t;
352 t.translate(dx: -tempxt, dy: -tempyt);
353 t.rotate(a: rotation);
354 t.scale(sx: scale, sy: scale);
355 t.translate(dx: tempxt, dy: tempyt);
356 const QPointF &offset = t.map(p: QPointF(0,0));
357 x += offset.x();
358 y += offset.y();
359 }
360
361 if (ok) {
362 //qDebug() << x << y << rotation << scale;
363 xAction.toValue = x;
364 yAction.toValue = y;
365 sAction.toValue = sAction.toValue.toReal() * scale;
366 rAction.toValue = rAction.toValue.toReal() + rotation;
367 }
368 }
369 }
370 }
371
372 if (data->actions.count()) {
373 QSequentialAnimationGroupJob *topLevelGroup = new QSequentialAnimationGroupJob;
374 QActionAnimation *viaAction = d->via ? new QActionAnimation : nullptr;
375 QActionAnimation *targetAction = new QActionAnimation;
376 //we'll assume the common case by far is to have children, and always create ag
377 QParallelAnimationGroupJob *ag = new QParallelAnimationGroupJob;
378
379 if (d->via)
380 viaAction->setAnimAction(viaData.release());
381 targetAction->setAnimAction(data.release());
382
383 //take care of any child animations
384 bool valid = d->defaultProperty.isValid();
385 QAbstractAnimationJob* anim;
386 for (int ii = 0; ii < d->animations.count(); ++ii) {
387 if (valid)
388 d->animations.at(i: ii)->setDefaultTarget(d->defaultProperty);
389 anim = d->animations.at(i: ii)->transition(actions, modified, direction, defaultTarget);
390 if (anim)
391 ag->appendAnimation(animation: anim);
392 }
393
394 //TODO: simplify/clarify logic
395 bool forwards = direction == QQuickAbstractAnimation::Forward;
396 if (forwards) {
397 topLevelGroup->appendAnimation(animation: d->via ? viaAction : targetAction);
398 topLevelGroup->appendAnimation(animation: ag);
399 if (d->via)
400 topLevelGroup->appendAnimation(animation: targetAction);
401 } else {
402 if (d->via)
403 topLevelGroup->appendAnimation(animation: targetAction);
404 topLevelGroup->appendAnimation(animation: ag);
405 topLevelGroup->appendAnimation(animation: d->via ? viaAction : targetAction);
406 }
407 return initInstance(animation: topLevelGroup);
408 }
409 return nullptr;
410}
411
412/*!
413 \qmltype AnchorAnimation
414 \instantiates QQuickAnchorAnimation
415 \inqmlmodule QtQuick
416 \ingroup qtquick-animation-properties
417 \inherits Animation
418 \brief Animates changes in anchor values.
419
420 AnchorAnimation is used to animate an anchor change.
421
422 In the following snippet we animate the addition of a right anchor to a \l Rectangle:
423
424 \snippet qml/anchoranimation.qml 0
425
426 When an AnchorAnimation is used in a \l Transition, it will
427 animate any AnchorChanges that have occurred during the state change.
428 This can be overridden by setting a specific target item using the
429 \l {AnchorChanges::target}{AnchorChanges.target} property.
430
431 \note AnchorAnimation can only be used in a \l Transition and in
432 conjunction with an AnchorChange. It cannot be used in behaviors and
433 other types of animations.
434
435 \sa {Animation and Transitions in Qt Quick}, AnchorChanges
436*/
437QQuickAnchorAnimation::QQuickAnchorAnimation(QObject *parent)
438: QQuickAbstractAnimation(*(new QQuickAnchorAnimationPrivate), parent)
439{
440}
441
442QQuickAnchorAnimation::~QQuickAnchorAnimation()
443{
444}
445
446/*!
447 \qmlproperty list<Item> QtQuick::AnchorAnimation::targets
448 The items to reanchor.
449
450 If no targets are specified all AnchorChanges will be
451 animated by the AnchorAnimation.
452*/
453QQmlListProperty<QQuickItem> QQuickAnchorAnimation::targets()
454{
455 Q_D(QQuickAnchorAnimation);
456 return QQmlListProperty<QQuickItem>(this, &(d->targets));
457}
458
459/*!
460 \qmlproperty int QtQuick::AnchorAnimation::duration
461 This property holds the duration of the animation, in milliseconds.
462
463 The default value is 250.
464*/
465int QQuickAnchorAnimation::duration() const
466{
467 Q_D(const QQuickAnchorAnimation);
468 return d->duration;
469}
470
471void QQuickAnchorAnimation::setDuration(int duration)
472{
473 if (duration < 0) {
474 qmlWarning(me: this) << tr(s: "Cannot set a duration of < 0");
475 return;
476 }
477
478 Q_D(QQuickAnchorAnimation);
479 if (d->duration == duration)
480 return;
481 d->duration = duration;
482 emit durationChanged(duration);
483}
484
485/*!
486 \qmlpropertygroup QtQuick::AnchorAnimation::easing
487 \qmlproperty enumeration QtQuick::AnchorAnimation::easing.type
488 \qmlproperty real QtQuick::AnchorAnimation::easing.amplitude
489 \qmlproperty real QtQuick::AnchorAnimation::easing.overshoot
490 \qmlproperty real QtQuick::AnchorAnimation::easing.period
491 \brief Specifies the easing curve used for the animation
492
493 To specify an easing curve you need to specify at least the type. For some curves you can also specify
494 amplitude, period and/or overshoot. The default easing curve is
495 Linear.
496
497 \qml
498 AnchorAnimation { easing.type: Easing.InOutQuad }
499 \endqml
500
501 See the \l{PropertyAnimation::easing.type} documentation for information
502 about the different types of easing curves.
503*/
504QEasingCurve QQuickAnchorAnimation::easing() const
505{
506 Q_D(const QQuickAnchorAnimation);
507 return d->easing;
508}
509
510void QQuickAnchorAnimation::setEasing(const QEasingCurve &e)
511{
512 Q_D(QQuickAnchorAnimation);
513 if (d->easing == e)
514 return;
515
516 d->easing = e;
517 emit easingChanged(e);
518}
519
520QAbstractAnimationJob* QQuickAnchorAnimation::transition(QQuickStateActions &actions,
521 QQmlProperties &modified,
522 TransitionDirection direction,
523 QObject *defaultTarget)
524{
525 Q_UNUSED(modified);
526 Q_UNUSED(defaultTarget);
527 Q_D(QQuickAnchorAnimation);
528 QQuickAnimationPropertyUpdater *data = new QQuickAnimationPropertyUpdater;
529 data->interpolatorType = QMetaType::QReal;
530 data->interpolator = d->interpolator;
531 data->reverse = direction == Backward ? true : false;
532 data->fromIsSourced = false;
533 data->fromIsDefined = false;
534
535 for (int ii = 0; ii < actions.count(); ++ii) {
536 QQuickStateAction &action = actions[ii];
537 if (action.event && action.event->type() == QQuickStateActionEvent::AnchorChanges
538 && (d->targets.isEmpty() || d->targets.contains(t: static_cast<QQuickAnchorChanges*>(action.event)->object()))) {
539 data->actions << static_cast<QQuickAnchorChanges*>(action.event)->additionalActions();
540 }
541 }
542
543 QQuickBulkValueAnimator *animator = new QQuickBulkValueAnimator;
544 if (data->actions.count()) {
545 animator->setAnimValue(data);
546 animator->setFromIsSourcedValue(&data->fromIsSourced);
547 } else {
548 delete data;
549 }
550
551 animator->setDuration(d->duration);
552 animator->setEasingCurve(d->easing);
553 return initInstance(animation: animator);
554}
555
556
557#if QT_CONFIG(quick_path)
558/*!
559 \qmltype PathAnimation
560 \instantiates QQuickPathAnimation
561 \inqmlmodule QtQuick
562 \ingroup qtquick-animation-properties
563 \inherits Animation
564 \since 5.0
565 \brief Animates an item along a path.
566
567 When used in a transition, the path can be specified without start
568 or end points, for example:
569 \qml
570 PathAnimation {
571 path: Path {
572 //no startX, startY
573 PathCurve { x: 100; y: 100}
574 PathCurve {} //last element is empty with no end point specified
575 }
576 }
577 \endqml
578
579 In the above case, the path start will be the item's current position, and the
580 path end will be the item's target position in the target state.
581
582 \sa {Animation and Transitions in Qt Quick}, PathInterpolator
583*/
584QQuickPathAnimation::QQuickPathAnimation(QObject *parent)
585: QQuickAbstractAnimation(*(new QQuickPathAnimationPrivate), parent)
586{
587}
588
589QQuickPathAnimation::~QQuickPathAnimation()
590{
591 typedef QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator ActiveAnimationsIt;
592
593 Q_D(QQuickPathAnimation);
594 for (ActiveAnimationsIt it = d->activeAnimations.begin(), end = d->activeAnimations.end(); it != end; ++it)
595 it.value()->clearTemplate();
596}
597
598/*!
599 \qmlproperty int QtQuick::PathAnimation::duration
600 This property holds the duration of the animation, in milliseconds.
601
602 The default value is 250.
603*/
604int QQuickPathAnimation::duration() const
605{
606 Q_D(const QQuickPathAnimation);
607 return d->duration;
608}
609
610void QQuickPathAnimation::setDuration(int duration)
611{
612 if (duration < 0) {
613 qmlWarning(me: this) << tr(s: "Cannot set a duration of < 0");
614 return;
615 }
616
617 Q_D(QQuickPathAnimation);
618 if (d->duration == duration)
619 return;
620 d->duration = duration;
621 emit durationChanged(duration);
622}
623
624/*!
625 \qmlpropertygroup QtQuick::PathAnimation::easing
626 \qmlproperty enumeration QtQuick::PathAnimation::easing.type
627 \qmlproperty real QtQuick::PathAnimation::easing.amplitude
628 \qmlproperty list<real> QtQuick::PathAnimation::easing.bezierCurve
629 \qmlproperty real QtQuick::PathAnimation::easing.overshoot
630 \qmlproperty real QtQuick::PathAnimation::easing.period
631 \brief the easing curve used for the animation.
632
633 To specify an easing curve you need to specify at least the type. For some curves you can also specify
634 amplitude, period, overshoot or custom bezierCurve data. The default easing curve is \c Easing.Linear.
635
636 See the \l{PropertyAnimation::easing.type} documentation for information
637 about the different types of easing curves.
638*/
639QEasingCurve QQuickPathAnimation::easing() const
640{
641 Q_D(const QQuickPathAnimation);
642 return d->easingCurve;
643}
644
645void QQuickPathAnimation::setEasing(const QEasingCurve &e)
646{
647 Q_D(QQuickPathAnimation);
648 if (d->easingCurve == e)
649 return;
650
651 d->easingCurve = e;
652 emit easingChanged(e);
653}
654
655/*!
656 \qmlproperty Path QtQuick::PathAnimation::path
657 This property holds the path to animate along.
658
659 For more information on defining a path see the \l Path documentation.
660*/
661QQuickPath *QQuickPathAnimation::path() const
662{
663 Q_D(const QQuickPathAnimation);
664 return d->path;
665}
666
667void QQuickPathAnimation::setPath(QQuickPath *path)
668{
669 Q_D(QQuickPathAnimation);
670 if (d->path == path)
671 return;
672
673 d->path = path;
674 emit pathChanged();
675}
676
677/*!
678 \qmlproperty Item QtQuick::PathAnimation::target
679 This property holds the item to animate.
680*/
681QQuickItem *QQuickPathAnimation::target() const
682{
683 Q_D(const QQuickPathAnimation);
684 return d->target;
685}
686
687void QQuickPathAnimation::setTargetObject(QQuickItem *target)
688{
689 Q_D(QQuickPathAnimation);
690 if (d->target == target)
691 return;
692
693 d->target = target;
694 emit targetChanged();
695}
696
697/*!
698 \qmlproperty enumeration QtQuick::PathAnimation::orientation
699 This property controls the rotation of the item as it animates along the path.
700
701 If a value other than \c Fixed is specified, the PathAnimation will rotate the
702 item to achieve the specified orientation as it travels along the path.
703
704 \list
705 \li PathAnimation.Fixed (default) - the PathAnimation will not control
706 the rotation of the item.
707 \li PathAnimation.RightFirst - The right side of the item will lead along the path.
708 \li PathAnimation.LeftFirst - The left side of the item will lead along the path.
709 \li PathAnimation.BottomFirst - The bottom of the item will lead along the path.
710 \li PathAnimation.TopFirst - The top of the item will lead along the path.
711 \endlist
712*/
713QQuickPathAnimation::Orientation QQuickPathAnimation::orientation() const
714{
715 Q_D(const QQuickPathAnimation);
716 return d->orientation;
717}
718
719void QQuickPathAnimation::setOrientation(Orientation orientation)
720{
721 Q_D(QQuickPathAnimation);
722 if (d->orientation == orientation)
723 return;
724
725 d->orientation = orientation;
726 emit orientationChanged(d->orientation);
727}
728
729/*!
730 \qmlproperty point QtQuick::PathAnimation::anchorPoint
731 This property holds the anchor point for the item being animated.
732
733 By default, the upper-left corner of the target (its 0,0 point)
734 will be anchored to (or follow) the path. The anchorPoint property can be used to
735 specify a different point for anchoring. For example, specifying an anchorPoint of
736 5,5 for a 10x10 item means the center of the item will follow the path.
737*/
738QPointF QQuickPathAnimation::anchorPoint() const
739{
740 Q_D(const QQuickPathAnimation);
741 return d->anchorPoint;
742}
743
744void QQuickPathAnimation::setAnchorPoint(const QPointF &point)
745{
746 Q_D(QQuickPathAnimation);
747 if (d->anchorPoint == point)
748 return;
749
750 d->anchorPoint = point;
751 emit anchorPointChanged(point);
752}
753
754/*!
755 \qmlproperty real QtQuick::PathAnimation::orientationEntryDuration
756 This property holds the duration (in milliseconds) of the transition in to the orientation.
757
758 If an orientation has been specified for the PathAnimation, and the starting
759 rotation of the item does not match that given by the orientation,
760 orientationEntryDuration can be used to smoothly transition from the item's
761 starting rotation to the rotation given by the path orientation.
762*/
763int QQuickPathAnimation::orientationEntryDuration() const
764{
765 Q_D(const QQuickPathAnimation);
766 return d->entryDuration;
767}
768
769void QQuickPathAnimation::setOrientationEntryDuration(int duration)
770{
771 Q_D(QQuickPathAnimation);
772 if (d->entryDuration == duration)
773 return;
774 d->entryDuration = duration;
775 emit orientationEntryDurationChanged(duration);
776}
777
778/*!
779 \qmlproperty real QtQuick::PathAnimation::orientationExitDuration
780 This property holds the duration (in milliseconds) of the transition out of the orientation.
781
782 If an orientation and endRotation have been specified for the PathAnimation,
783 orientationExitDuration can be used to smoothly transition from the rotation given
784 by the path orientation to the specified endRotation.
785*/
786int QQuickPathAnimation::orientationExitDuration() const
787{
788 Q_D(const QQuickPathAnimation);
789 return d->exitDuration;
790}
791
792void QQuickPathAnimation::setOrientationExitDuration(int duration)
793{
794 Q_D(QQuickPathAnimation);
795 if (d->exitDuration == duration)
796 return;
797 d->exitDuration = duration;
798 emit orientationExitDurationChanged(duration);
799}
800
801/*!
802 \qmlproperty real QtQuick::PathAnimation::endRotation
803 This property holds the ending rotation for the target.
804
805 If an orientation has been specified for the PathAnimation,
806 and the path doesn't end with the item at the desired rotation,
807 the endRotation property can be used to manually specify an end
808 rotation.
809
810 This property is typically used with orientationExitDuration, as specifying
811 an endRotation without an orientationExitDuration may cause a jump to
812 the final rotation, rather than a smooth transition.
813*/
814qreal QQuickPathAnimation::endRotation() const
815{
816 Q_D(const QQuickPathAnimation);
817 return d->endRotation.isNull ? qreal(0) : d->endRotation.value;
818}
819
820void QQuickPathAnimation::setEndRotation(qreal rotation)
821{
822 Q_D(QQuickPathAnimation);
823 if (!d->endRotation.isNull && d->endRotation == rotation)
824 return;
825
826 d->endRotation = rotation;
827 emit endRotationChanged(d->endRotation);
828}
829
830QAbstractAnimationJob* QQuickPathAnimation::transition(QQuickStateActions &actions,
831 QQmlProperties &modified,
832 TransitionDirection direction,
833 QObject *defaultTarget)
834{
835 Q_D(QQuickPathAnimation);
836
837 QQuickItem *target = d->target ? d->target : qobject_cast<QQuickItem*>(object: defaultTarget);
838
839 QQuickPathAnimationUpdater prevData;
840 bool havePrevData = false;
841 if (d->activeAnimations.contains(key: target)) {
842 havePrevData = true;
843 prevData = *d->activeAnimations[target]->pathUpdater();
844 }
845
846 for (auto it = d->activeAnimations.begin(); it != d->activeAnimations.end();) {
847 QQuickPathAnimationAnimator *anim = it.value();
848 if (anim->state() == QAbstractAnimationJob::Stopped) {
849 anim->clearTemplate();
850 it = d->activeAnimations.erase(it);
851 } else {
852 ++it;
853 }
854 }
855
856 QQuickPathAnimationUpdater *data = new QQuickPathAnimationUpdater();
857 QQuickPathAnimationAnimator *pa = new QQuickPathAnimationAnimator(d);
858
859 d->activeAnimations[target] = pa;
860
861 data->orientation = d->orientation;
862 data->anchorPoint = d->anchorPoint;
863 data->entryInterval = d->duration ? qreal(d->entryDuration) / d->duration : qreal(0);
864 data->exitInterval = d->duration ? qreal(d->exitDuration) / d->duration : qreal(0);
865 data->endRotation = d->endRotation;
866 data->reverse = direction == Backward ? true : false;
867 data->fromIsSourced = false;
868 data->fromIsDefined = (d->path && d->path->hasStartX() && d->path->hasStartY()) ? true : false;
869 data->toIsDefined = d->path ? true : false;
870 int origModifiedSize = modified.count();
871
872 for (int i = 0; i < actions.count(); ++i) {
873 QQuickStateAction &action = actions[i];
874 if (action.event)
875 continue;
876 if (action.specifiedObject == target && action.property.name() == QLatin1String("x")) {
877 data->toX = action.toValue.toReal();
878 modified << action.property;
879 action.fromValue = action.toValue;
880 }
881 if (action.specifiedObject == target && action.property.name() == QLatin1String("y")) {
882 data->toY = action.toValue.toReal();
883 modified << action.property;
884 action.fromValue = action.toValue;
885 }
886 }
887
888 if (target && d->path && (modified.count() > origModifiedSize || data->toIsDefined)) {
889 data->target = target;
890 data->path = d->path;
891 data->path->invalidateSequentialHistory();
892
893 if (havePrevData) {
894 // get the original start angle that was used (so we can exactly reverse).
895 data->startRotation = prevData.startRotation;
896
897 // treat interruptions specially, otherwise we end up with strange paths
898 if ((data->reverse || prevData.reverse) && prevData.currentV > 0 && prevData.currentV < 1) {
899 if (!data->fromIsDefined && !data->toIsDefined && !prevData.painterPath.isEmpty()) {
900 QPointF pathPos = QQuickPath::sequentialPointAt(path: prevData.painterPath, pathLength: prevData.pathLength, attributePoints: prevData.attributePoints, prevBez&: prevData.prevBez, p: prevData.currentV);
901 if (!prevData.anchorPoint.isNull())
902 pathPos -= prevData.anchorPoint;
903 if (pathPos == data->target->position()) { //only treat as interruption if we interrupted ourself
904 data->painterPath = prevData.painterPath;
905 data->toIsDefined = data->fromIsDefined = data->fromIsSourced = true;
906 data->prevBez.isValid = false;
907 data->interruptStart = prevData.currentV;
908 data->startRotation = prevData.startRotation;
909 data->pathLength = prevData.pathLength;
910 data->attributePoints = prevData.attributePoints;
911 }
912 }
913 }
914 }
915 pa->setFromIsSourcedValue(&data->fromIsSourced);
916 pa->setAnimValue(data);
917 pa->setDuration(d->duration);
918 pa->setEasingCurve(d->easingCurve);
919 return initInstance(animation: pa);
920 } else {
921 pa->setFromIsSourcedValue(nullptr);
922 pa->setAnimValue(nullptr);
923 delete pa;
924 delete data;
925 }
926 return nullptr;
927}
928
929void QQuickPathAnimationUpdater::setValue(qreal v)
930{
931 v = qMin(a: qMax(a: v, b: (qreal)0.0), b: (qreal)1.0);;
932
933 if (interruptStart.isValid()) {
934 if (reverse)
935 v = 1 - v;
936 qreal end = reverse ? 0.0 : 1.0;
937 v = interruptStart + v * (end-interruptStart);
938 }
939 currentV = v;
940 bool atStart = ((reverse && v == 1.0) || (!reverse && v == 0.0));
941 if (!fromIsSourced && (!fromIsDefined || !toIsDefined)) {
942 qreal startX = reverse ? toX + anchorPoint.x() : target->x() + anchorPoint.x();
943 qreal startY = reverse ? toY + anchorPoint.y() : target->y() + anchorPoint.y();
944 qreal endX = reverse ? target->x() + anchorPoint.x() : toX + anchorPoint.x();
945 qreal endY = reverse ? target->y() + anchorPoint.y() : toY + anchorPoint.y();
946
947 prevBez.isValid = false;
948 painterPath = path->createPath(startPoint: QPointF(startX, startY), endPoint: QPointF(endX, endY), attributes: QStringList(), pathLength, attributePoints);
949 fromIsSourced = true;
950 }
951
952 qreal angle;
953 bool fixed = orientation == QQuickPathAnimation::Fixed;
954 QPointF currentPos = !painterPath.isEmpty() ? path->sequentialPointAt(path: painterPath, pathLength, attributePoints, prevBez, p: v, angle: fixed ? nullptr : &angle) : path->sequentialPointAt(p: v, angle: fixed ? nullptr : &angle);
955
956 //adjust position according to anchor point
957 if (!anchorPoint.isNull()) {
958 currentPos -= anchorPoint;
959 if (atStart) {
960 if (!anchorPoint.isNull() && !fixed)
961 target->setTransformOriginPoint(anchorPoint);
962 }
963 }
964
965 target->setPosition(currentPos);
966
967 //adjust angle according to orientation
968 if (!fixed) {
969 switch (orientation) {
970 case QQuickPathAnimation::RightFirst:
971 angle = -angle;
972 break;
973 case QQuickPathAnimation::TopFirst:
974 angle = -angle + 90;
975 break;
976 case QQuickPathAnimation::LeftFirst:
977 angle = -angle + 180;
978 break;
979 case QQuickPathAnimation::BottomFirst:
980 angle = -angle + 270;
981 break;
982 default:
983 angle = 0;
984 break;
985 }
986
987 if (atStart && !reverse) {
988 startRotation = target->rotation();
989
990 //shortest distance to correct orientation
991 qreal diff = angle - startRotation;
992 while (diff > 180.0) {
993 startRotation.value += 360.0;
994 diff -= 360.0;
995 }
996 while (diff < -180.0) {
997 startRotation.value -= 360.0;
998 diff += 360.0;
999 }
1000 }
1001
1002 //smoothly transition to the desired orientation
1003 //TODO: shortest distance calculations
1004 if (startRotation.isValid()) {
1005 if (reverse && v == 0.0)
1006 angle = startRotation;
1007 else if (v < entryInterval)
1008 angle = angle * v / entryInterval + startRotation * (entryInterval - v) / entryInterval;
1009 }
1010 if (endRotation.isValid()) {
1011 qreal exitStart = 1 - entryInterval;
1012 if (!reverse && v == 1.0)
1013 angle = endRotation;
1014 else if (v > exitStart)
1015 angle = endRotation * (v - exitStart) / exitInterval + angle * (exitInterval - (v - exitStart)) / exitInterval;
1016 }
1017 target->setRotation(angle);
1018 }
1019
1020 /*
1021 NOTE: we don't always reset the transform origin, as it can cause a
1022 visual jump if ending on an angle. This means that in some cases
1023 (anchor point and orientation both specified, and ending at an angle)
1024 the transform origin will always be set after running the path animation.
1025 */
1026 if ((reverse && v == 0.0) || (!reverse && v == 1.0)) {
1027 if (!anchorPoint.isNull() && !fixed && qFuzzyIsNull(d: angle))
1028 target->setTransformOriginPoint(QPointF());
1029 }
1030}
1031
1032QQuickPathAnimationAnimator::QQuickPathAnimationAnimator(QQuickPathAnimationPrivate *priv)
1033 : animationTemplate(priv)
1034{
1035}
1036
1037QQuickPathAnimationAnimator::~QQuickPathAnimationAnimator()
1038{
1039 if (animationTemplate && pathUpdater()) {
1040 QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator it =
1041 animationTemplate->activeAnimations.find(key: pathUpdater()->target);
1042 if (it != animationTemplate->activeAnimations.end() && it.value() == this)
1043 animationTemplate->activeAnimations.erase(it);
1044 }
1045}
1046
1047#endif // quick_path
1048
1049QT_END_NAMESPACE
1050
1051#include "moc_qquickitemanimation_p.cpp"
1052

source code of qtdeclarative/src/quick/items/qquickitemanimation.cpp