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

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