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