Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickitemanimation_p.h"
++ #include "qquickitemanimation_p_p.h"
++ #include "qquickstateoperations_p.h"
++
++ #include <private/qqmlproperty_p.h>
++ #if QT_CONFIG(quick_path)
++ #include <private/qquickpath_p.h>
++ #endif
++ #include "private/qparallelanimationgroupjob_p.h"
++ #include "private/qsequentialanimationgroupjob_p.h"
++
++ #include <QtCore/qmath.h>
++ #include <QtGui/qtransform.h>
++ #include <QtQml/qqmlinfo.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype ParentAnimation
++ \nativetype QQuickParentAnimation
++ \inqmlmodule QtQuick
++ \ingroup qtquick-animation-properties
++ \since 5.0
++ \inherits Animation
++ \brief Animates changes in parent values.
++
++ ParentAnimation is used to animate a parent change for an \l Item.
++
++ For example, the following ParentChange changes \c blueRect to become
++ a child of \c redRect when it is clicked. The inclusion of the
++ ParentAnimation, which defines a NumberAnimation to be applied during
++ the transition, ensures the item animates smoothly as it moves to
++ its new parent:
++
++ \snippet qml/parentanimation.qml 0
++
++ A ParentAnimation can contain any number of animations. These animations will
++ be run in parallel; to run them sequentially, define them within a
++ SequentialAnimation.
++
++ In some cases, such as when reparenting between items with clipping enabled, it is useful
++ to animate the parent change via another item that does not have clipping
++ enabled. Such an item can be set using the \l via property.
++
++ ParentAnimation is typically used within a \l Transition in conjunction
++ with a ParentChange. When used in this manner, it animates any
++ ParentChange that has occurred during the state change. This can be
++ overridden by setting a specific target item using the \l target property.
++
++ \sa {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation}
++ */
++ QQuickParentAnimation::QQuickParentAnimation(QObject *parent)
++ : QQuickAnimationGroup(*(new QQuickParentAnimationPrivate), parent)
++ {
++ }
++
++ /*!
++ \qmlproperty Item QtQuick::ParentAnimation::target
++ The item to reparent.
++
++ When used in a transition, if no target is specified, all
++ ParentChange occurrences are animated by the ParentAnimation.
++ */
++ QQuickItem *QQuickParentAnimation::target() const
++ {
++ Q_D(const QQuickParentAnimation);
++ return d->target;
++ }
++
++ void QQuickParentAnimation::setTargetObject(QQuickItem *target)
++ {
++ Q_D(QQuickParentAnimation);
++ if (target == d->target)
++ return;
++
++ d->target = target;
++ emit targetChanged();
++ }
++
++ /*!
++ \qmlproperty Item QtQuick::ParentAnimation::newParent
++ The new parent to animate to.
++
++ If the ParentAnimation is defined within a \l Transition,
++ this value defaults to the value defined in the end state of the
++ \l Transition.
++ */
++ QQuickItem *QQuickParentAnimation::newParent() const
++ {
++ Q_D(const QQuickParentAnimation);
++ return d->newParent;
++ }
++
++ void QQuickParentAnimation::setNewParent(QQuickItem *newParent)
++ {
++ Q_D(QQuickParentAnimation);
++ if (newParent == d->newParent)
++ return;
++
++ d->newParent = newParent;
++ emit newParentChanged();
++ }
++
++ /*!
++ \qmlproperty Item QtQuick::ParentAnimation::via
++ The item to reparent via. This provides a way to do an unclipped animation
++ when both the old parent and new parent are clipped.
++
++ \qml
++ ParentAnimation {
++ target: myItem
++ via: topLevelItem
++ // ...
++ }
++ \endqml
++
++ \note This only works when the ParentAnimation is used in a \l Transition
++ in conjunction with a ParentChange.
++ */
++ QQuickItem *QQuickParentAnimation::via() const
++ {
++ Q_D(const QQuickParentAnimation);
++ return d->via;
++ }
++
++ void QQuickParentAnimation::setVia(QQuickItem *via)
++ {
++ Q_D(QQuickParentAnimation);
++ if (via == d->via)
++ return;
++
++ d->via = via;
++ emit viaChanged();
++ }
++
++ //### mirrors same-named function in QQuickItem
++ QPointF QQuickParentAnimationPrivate::computeTransformOrigin(QQuickItem::TransformOrigin origin, qreal width, qreal height) const
++ {
++ switch (origin) {
++ default:
++ case QQuickItem::TopLeft:
++ return QPointF(0, 0);
++ case QQuickItem::Top:
++ return QPointF(width / 2., 0);
++ case QQuickItem::TopRight:
++ return QPointF(width, 0);
++ case QQuickItem::Left:
++ return QPointF(0, height / 2.);
++ case QQuickItem::Center:
++ return QPointF(width / 2., height / 2.);
++ case QQuickItem::Right:
++ return QPointF(width, height / 2.);
++ case QQuickItem::BottomLeft:
++ return QPointF(0, height);
++ case QQuickItem::Bottom:
++ return QPointF(width / 2., height);
++ case QQuickItem::BottomRight:
++ return QPointF(width, height);
++ }
++ }
++
++ struct QQuickParentAnimationData : public QAbstractAnimationAction
++ {
++ QQuickParentAnimationData() : reverse(false) {}
++ ~QQuickParentAnimationData() { qDeleteAll(pc); }
++
++ QQuickStateActions actions;
++ //### reverse should probably apply on a per-action basis
++ bool reverse;
++ QList<QQuickParentChange *> pc;
++ void doAction() override
++ {
++ for (int ii = 0; ii < actions.size(); ++ii) {
++ const QQuickStateAction &action = actions.at(ii);
++ if (reverse)
++ action.event->reverse();
++ else
++ action.event->execute();
++ }
++ }
++ };
++
++ QAbstractAnimationJob* QQuickParentAnimation::transition(QQuickStateActions &actions,
++ QQmlProperties &modified,
++ TransitionDirection direction,
++ QObject *defaultTarget)
++ {
++ Q_D(QQuickParentAnimation);
++
++ std::unique_ptr<QQuickParentAnimationData> data(new QQuickParentAnimationData);
++ std::unique_ptr<QQuickParentAnimationData> viaData(new QQuickParentAnimationData);
++
++ bool hasExplicit = false;
++ if (d->target && d->newParent) {
++ data->reverse = false;
++ QQuickStateAction myAction;
++ QQuickParentChange *pc = new QQuickParentChange;
++ pc->setObject(d->target);
++ pc->setParent(d->newParent);
++ myAction.event = pc;
++ data->pc << pc;
++ data->actions << myAction;
++ hasExplicit = true;
++ if (d->via) {
++ viaData->reverse = false;
++ QQuickStateAction myVAction;
++ QQuickParentChange *vpc = new QQuickParentChange;
++ vpc->setObject(d->target);
++ vpc->setParent(d->via);
++ myVAction.event = vpc;
++ viaData->pc << vpc;
++ viaData->actions << myVAction;
++ }
++ //### once actions have concept of modified,
++ // loop to match appropriate ParentChanges and mark as modified
++ }
++
++ if (!hasExplicit)
++ for (int i = 0; i < actions.size(); ++i) {
++ QQuickStateAction &action = actions[i];
++ if (action.event && action.event->type() == QQuickStateActionEvent::ParentChange
++ && (!d->target || static_cast<QQuickParentChange*>(action.event)->object() == d->target)) {
++
++ QQuickParentChange *pc = static_cast<QQuickParentChange*>(action.event);
++ QQuickStateAction myAction = action;
++ data->reverse = action.reverseEvent;
++
++ //### this logic differs from PropertyAnimation
++ // (probably a result of modified vs. done)
++ if (d->newParent) {
++ QQuickParentChange *epc = new QQuickParentChange;
++ epc->setObject(static_cast<QQuickParentChange*>(action.event)->object());
++ epc->setParent(d->newParent);
++ myAction.event = epc;
++ data->pc << epc;
++ data->actions << myAction;
++ pc = epc;
++ } else {
++ action.actionDone = true;
++ data->actions << myAction;
++ }
++
++ if (d->via) {
++ viaData->reverse = false;
++ QQuickStateAction myAction;
++ QQuickParentChange *vpc = new QQuickParentChange;
++ vpc->setObject(pc->object());
++ vpc->setParent(d->via);
++ myAction.event = vpc;
++ viaData->pc << vpc;
++ viaData->actions << myAction;
++ QQuickStateAction dummyAction;
++ QQuickStateAction &xAction = pc->xIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
++ QQuickStateAction &yAction = pc->yIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
++ QQuickStateAction &sAction = pc->scaleIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
++ QQuickStateAction &rAction = pc->rotationIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction;
++ QQuickItem *target = pc->object();
++ QQuickItem *targetParent = action.reverseEvent ? pc->originalParent() : pc->parent();
++
++ //### this mirrors the logic in QQuickParentChange.
++ bool ok;
++ const QTransform &transform = targetParent->itemTransform(d->via, &ok);
++ if (transform.type() >= QTransform::TxShear || !ok) {
++ qmlWarning(this) << QQuickParentAnimation::tr("Unable to preserve appearance under complex transform");
++ ok = false;
++ }
++
++ qreal scale = 1;
++ qreal rotation = 0;
++ bool isRotate = (transform.type() == QTransform::TxRotate) || (transform.m11() < 0);
++ if (ok && !isRotate) {
++ if (transform.m11() == transform.m22())
++ scale = transform.m11();
++ else {
++ qmlWarning(this) << QQuickParentAnimation::tr("Unable to preserve appearance under non-uniform scale");
++ ok = false;
++ }
++ } else if (ok && isRotate) {
++ if (transform.m11() == transform.m22())
++ scale = qSqrt(transform.m11()*transform.m11() + transform.m12()*transform.m12());
++ else {
++ qmlWarning(this) << QQuickParentAnimation::tr("Unable to preserve appearance under non-uniform scale");
++ ok = false;
++ }
++
++ if (scale != 0)
++ rotation = qRadiansToDegrees(qAtan2(transform.m12() / scale, transform.m11() / scale));
++ else {
++ qmlWarning(this) << QQuickParentAnimation::tr("Unable to preserve appearance under scale of 0");
++ ok = false;
++ }
++ }
++
++ const QPointF &point = transform.map(QPointF(xAction.toValue.toReal(),yAction.toValue.toReal()));
++ qreal x = point.x();
++ qreal y = point.y();
++ if (ok && target->transformOrigin() != QQuickItem::TopLeft) {
++ qreal w = target->width();
++ qreal h = target->height();
++ if (pc->widthIsSet() && i < actions.size() - 1)
++ w = actions.at(++i).toValue.toReal();
++ if (pc->heightIsSet() && i < actions.size() - 1)
++ h = actions.at(++i).toValue.toReal();
++ const QPointF &transformOrigin
++ = d->computeTransformOrigin(target->transformOrigin(), w,h);
++ qreal tempxt = transformOrigin.x();
++ qreal tempyt = transformOrigin.y();
++ QTransform t;
++ t.translate(-tempxt, -tempyt);
++ t.rotate(rotation);
++ t.scale(scale, scale);
++ t.translate(tempxt, tempyt);
++ const QPointF &offset = t.map(QPointF(0,0));
++ x += offset.x();
++ y += offset.y();
++ }
++
++ if (ok) {
++ //qDebug() << x << y << rotation << scale;
++ xAction.toValue = x;
++ yAction.toValue = y;
++ sAction.toValue = sAction.toValue.toReal() * scale;
++ rAction.toValue = rAction.toValue.toReal() + rotation;
++ }
++ }
++ }
++ }
++
++ if (data->actions.size()) {
++ QSequentialAnimationGroupJob *topLevelGroup = new QSequentialAnimationGroupJob;
++ QActionAnimation *viaAction = d->via ? new QActionAnimation : nullptr;
++ QActionAnimation *targetAction = new QActionAnimation;
++ //we'll assume the common case by far is to have children, and always create ag
++ QParallelAnimationGroupJob *ag = new QParallelAnimationGroupJob;
++
++ if (d->via)
++ viaAction->setAnimAction(viaData.release());
++ targetAction->setAnimAction(data.release());
++
++ //take care of any child animations
++ bool valid = d->defaultProperty.isValid();
++ QAbstractAnimationJob* anim;
++ for (int ii = 0; ii < d->animations.size(); ++ii) {
++ if (valid)
++ d->animations.at(ii)->setDefaultTarget(d->defaultProperty);
++ anim = d->animations.at(ii)->transition(actions, modified, direction, defaultTarget);
++ if (anim)
++ ag->appendAnimation(anim);
++ }
++
++ //TODO: simplify/clarify logic
++ bool forwards = direction == QQuickAbstractAnimation::Forward;
++ if (forwards) {
++ topLevelGroup->appendAnimation(d->via ? viaAction : targetAction);
++ topLevelGroup->appendAnimation(ag);
++ if (d->via)
++ topLevelGroup->appendAnimation(targetAction);
++ } else {
++ if (d->via)
++ topLevelGroup->appendAnimation(targetAction);
++ topLevelGroup->appendAnimation(ag);
++ topLevelGroup->appendAnimation(d->via ? viaAction : targetAction);
++ }
++ return initInstance(topLevelGroup);
++ }
++ return nullptr;
++ }
++
++ /*!
++ \qmltype AnchorAnimation
++ \nativetype QQuickAnchorAnimation
++ \inqmlmodule QtQuick
++ \ingroup qtquick-animation-properties
++ \inherits Animation
++ \brief Animates changes in anchor values.
++
++ AnchorAnimation is used to animate an anchor change.
++
++ In the following snippet we animate the addition of a right anchor to a \l Rectangle:
++
++ \snippet qml/anchoranimation.qml 0
++
++ When an AnchorAnimation is used in a \l Transition, it will
++ animate any AnchorChanges that have occurred during the state change.
++ This can be overridden by setting a specific target item using the
++ \l {AnchorChanges::target}{AnchorChanges.target} property.
++
++ \note AnchorAnimation can only be used in a \l Transition and in
++ conjunction with an AnchorChange. It cannot be used in behaviors and
++ other types of animations.
++
++ \sa {Animation and Transitions in Qt Quick}, AnchorChanges
++ */
++ QQuickAnchorAnimation::QQuickAnchorAnimation(QObject *parent)
++ : QQuickAbstractAnimation(*(new QQuickAnchorAnimationPrivate), parent)
++ {
++ }
++
++ /*!
++ \qmlproperty list<Item> QtQuick::AnchorAnimation::targets
++ The items to reanchor.
++
++ If no targets are specified all AnchorChanges will be
++ animated by the AnchorAnimation.
++ */
++ QQmlListProperty<QQuickItem> QQuickAnchorAnimation::targets()
++ {
++ Q_D(QQuickAnchorAnimation);
++ return QQmlListProperty<QQuickItem>(this, &(d->targets));
++ }
++
++ /*!
++ \qmlproperty int QtQuick::AnchorAnimation::duration
++ This property holds the duration of the animation, in milliseconds.
++
++ The default value is 250.
++ */
++ int QQuickAnchorAnimation::duration() const
++ {
++ Q_D(const QQuickAnchorAnimation);
++ return d->duration;
++ }
++
++ void QQuickAnchorAnimation::setDuration(int duration)
++ {
++ if (duration < 0) {
++ qmlWarning(this) << tr("Cannot set a duration of < 0");
++ return;
++ }
++
++ Q_D(QQuickAnchorAnimation);
++ if (d->duration == duration)
++ return;
++ d->duration = duration;
++ emit durationChanged(duration);
++ }
++
++ /*!
++ \qmlpropertygroup QtQuick::AnchorAnimation::easing
++ \qmlproperty enumeration QtQuick::AnchorAnimation::easing.type
++ \qmlproperty real QtQuick::AnchorAnimation::easing.amplitude
++ \qmlproperty real QtQuick::AnchorAnimation::easing.overshoot
++ \qmlproperty real QtQuick::AnchorAnimation::easing.period
++ \brief Specifies the easing curve used for the animation
++
++ To specify an easing curve you need to specify at least the type. For some curves you can also specify
++ amplitude, period and/or overshoot. The default easing curve is
++ Linear.
++
++ \qml
++ AnchorAnimation { easing.type: Easing.InOutQuad }
++ \endqml
++
++ See the \l{PropertyAnimation::easing.type} documentation for information
++ about the different types of easing curves.
++ */
++ QEasingCurve QQuickAnchorAnimation::easing() const
++ {
++ Q_D(const QQuickAnchorAnimation);
++ return d->easing;
++ }
++
++ void QQuickAnchorAnimation::setEasing(const QEasingCurve &e)
++ {
++ Q_D(QQuickAnchorAnimation);
++ if (d->easing == e)
++ return;
++
++ d->easing = e;
++ emit easingChanged(e);
++ }
++
++ QAbstractAnimationJob* QQuickAnchorAnimation::transition(QQuickStateActions &actions,
++ QQmlProperties &modified,
++ TransitionDirection direction,
++ QObject *defaultTarget)
++ {
++ Q_UNUSED(modified);
++ Q_UNUSED(defaultTarget);
++ Q_D(QQuickAnchorAnimation);
++ QQuickAnimationPropertyUpdater *data = new QQuickAnimationPropertyUpdater;
++ data->interpolatorType = QMetaType::QReal;
++ data->interpolator = d->interpolator;
++ data->reverse = direction == Backward ? true : false;
++ data->fromIsSourced = false;
++ data->fromIsDefined = false;
++
++ for (int ii = 0; ii < actions.size(); ++ii) {
++ QQuickStateAction &action = actions[ii];
++ if (action.event && action.event->type() == QQuickStateActionEvent::AnchorChanges
++ && (d->targets.isEmpty() || d->targets.contains(static_cast<QQuickAnchorChanges*>(action.event)->object()))) {
++ data->actions << static_cast<QQuickAnchorChanges*>(action.event)->additionalActions();
++ }
++ }
++
++ QQuickBulkValueAnimator *animator = new QQuickBulkValueAnimator;
++ if (data->actions.size()) {
++ animator->setAnimValue(data);
++ animator->setFromIsSourcedValue(&data->fromIsSourced);
++ } else {
++ delete data;
++ }
++
++ animator->setDuration(d->duration);
++ animator->setEasingCurve(d->easing);
++ return initInstance(animator);
++ }
++
++
++ #if QT_CONFIG(quick_path)
++ /*!
++ \qmltype PathAnimation
++ \nativetype QQuickPathAnimation
++ \inqmlmodule QtQuick
++ \ingroup qtquick-animation-properties
++ \inherits Animation
++ \since 5.0
++ \brief Animates an item along a path.
++
++ When used in a transition, the path can be specified without start
++ or end points, for example:
++ \qml
++ PathAnimation {
++ path: Path {
++ //no startX, startY
++ PathCurve { x: 100; y: 100}
++ PathCurve {} //last element is empty with no end point specified
++ }
++ }
++ \endqml
++
++ In the above case, the path start will be the item's current position, and the
++ path end will be the item's target position in the target state.
++
++ \sa {Animation and Transitions in Qt Quick}, PathInterpolator
++ */
++ QQuickPathAnimation::QQuickPathAnimation(QObject *parent)
++ : QQuickAbstractAnimation(*(new QQuickPathAnimationPrivate), parent)
++ {
++ }
++
++ QQuickPathAnimation::~QQuickPathAnimation()
++ {
++ typedef QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator ActiveAnimationsIt;
++
++ Q_D(QQuickPathAnimation);
++ for (ActiveAnimationsIt it = d->activeAnimations.begin(), end = d->activeAnimations.end(); it != end; ++it)
++ it.value()->clearTemplate();
++ }
++
++ /*!
++ \qmlproperty int QtQuick::PathAnimation::duration
++ This property holds the duration of the animation, in milliseconds.
++
++ The default value is 250.
++ */
++ int QQuickPathAnimation::duration() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->duration;
++ }
++
++ void QQuickPathAnimation::setDuration(int duration)
++ {
++ if (duration < 0) {
++ qmlWarning(this) << tr("Cannot set a duration of < 0");
++ return;
++ }
++
++ Q_D(QQuickPathAnimation);
++ if (d->duration == duration)
++ return;
++ d->duration = duration;
++ emit durationChanged(duration);
++ }
++
++ /*!
++ \qmlpropertygroup QtQuick::PathAnimation::easing
++ \qmlproperty enumeration QtQuick::PathAnimation::easing.type
++ \qmlproperty real QtQuick::PathAnimation::easing.amplitude
++ \qmlproperty list<real> QtQuick::PathAnimation::easing.bezierCurve
++ \qmlproperty real QtQuick::PathAnimation::easing.overshoot
++ \qmlproperty real QtQuick::PathAnimation::easing.period
++ \brief the easing curve used for the animation.
++
++ To specify an easing curve you need to specify at least the type. For some curves you can also specify
++ amplitude, period, overshoot or custom bezierCurve data. The default easing curve is \c Easing.Linear.
++
++ See the \l{PropertyAnimation::easing.type} documentation for information
++ about the different types of easing curves.
++ */
++ QEasingCurve QQuickPathAnimation::easing() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->easingCurve;
++ }
++
++ void QQuickPathAnimation::setEasing(const QEasingCurve &e)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->easingCurve == e)
++ return;
++
++ d->easingCurve = e;
++ emit easingChanged(e);
++ }
++
++ /*!
++ \qmlproperty Path QtQuick::PathAnimation::path
++ This property holds the path to animate along.
++
++ For more information on defining a path see the \l Path documentation.
++ */
++ QQuickPath *QQuickPathAnimation::path() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->path;
++ }
++
++ void QQuickPathAnimation::setPath(QQuickPath *path)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->path == path)
++ return;
++
++ d->path = path;
++ emit pathChanged();
++ }
++
++ /*!
++ \qmlproperty Item QtQuick::PathAnimation::target
++ This property holds the item to animate.
++ */
++ QQuickItem *QQuickPathAnimation::target() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->target;
++ }
++
++ void QQuickPathAnimation::setTargetObject(QQuickItem *target)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->target == target)
++ return;
++
++ d->target = target;
++ emit targetChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration QtQuick::PathAnimation::orientation
++ This property controls the rotation of the item as it animates along the path.
++
++ If a value other than \c Fixed is specified, the PathAnimation will rotate the
++ item to achieve the specified orientation as it travels along the path.
++
++ \list
++ \li PathAnimation.Fixed (default) - the PathAnimation will not control
++ the rotation of the item.
++ \li PathAnimation.RightFirst - The right side of the item will lead along the path.
++ \li PathAnimation.LeftFirst - The left side of the item will lead along the path.
++ \li PathAnimation.BottomFirst - The bottom of the item will lead along the path.
++ \li PathAnimation.TopFirst - The top of the item will lead along the path.
++ \endlist
++ */
++ QQuickPathAnimation::Orientation QQuickPathAnimation::orientation() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->orientation;
++ }
++
++ void QQuickPathAnimation::setOrientation(Orientation orientation)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->orientation == orientation)
++ return;
++
++ d->orientation = orientation;
++ emit orientationChanged(d->orientation);
++ }
++
++ /*!
++ \qmlproperty point QtQuick::PathAnimation::anchorPoint
++ This property holds the anchor point for the item being animated.
++
++ By default, the upper-left corner of the target (its 0,0 point)
++ will be anchored to (or follow) the path. The anchorPoint property can be used to
++ specify a different point for anchoring. For example, specifying an anchorPoint of
++ 5,5 for a 10x10 item means the center of the item will follow the path.
++ */
++ QPointF QQuickPathAnimation::anchorPoint() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->anchorPoint;
++ }
++
++ void QQuickPathAnimation::setAnchorPoint(const QPointF &point)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->anchorPoint == point)
++ return;
++
++ d->anchorPoint = point;
++ emit anchorPointChanged(point);
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathAnimation::orientationEntryDuration
++ This property holds the duration (in milliseconds) of the transition in to the orientation.
++
++ If an orientation has been specified for the PathAnimation, and the starting
++ rotation of the item does not match that given by the orientation,
++ orientationEntryDuration can be used to smoothly transition from the item's
++ starting rotation to the rotation given by the path orientation.
++ */
++ int QQuickPathAnimation::orientationEntryDuration() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->entryDuration;
++ }
++
++ void QQuickPathAnimation::setOrientationEntryDuration(int duration)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->entryDuration == duration)
++ return;
++ d->entryDuration = duration;
++ emit orientationEntryDurationChanged(duration);
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathAnimation::orientationExitDuration
++ This property holds the duration (in milliseconds) of the transition out of the orientation.
++
++ If an orientation and endRotation have been specified for the PathAnimation,
++ orientationExitDuration can be used to smoothly transition from the rotation given
++ by the path orientation to the specified endRotation.
++ */
++ int QQuickPathAnimation::orientationExitDuration() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->exitDuration;
++ }
++
++ void QQuickPathAnimation::setOrientationExitDuration(int duration)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->exitDuration == duration)
++ return;
++ d->exitDuration = duration;
++ emit orientationExitDurationChanged(duration);
++ }
++
++ /*!
++ \qmlproperty real QtQuick::PathAnimation::endRotation
++ This property holds the ending rotation for the target.
++
++ If an orientation has been specified for the PathAnimation,
++ and the path doesn't end with the item at the desired rotation,
++ the endRotation property can be used to manually specify an end
++ rotation.
++
++ This property is typically used with orientationExitDuration, as specifying
++ an endRotation without an orientationExitDuration may cause a jump to
++ the final rotation, rather than a smooth transition.
++ */
++ qreal QQuickPathAnimation::endRotation() const
++ {
++ Q_D(const QQuickPathAnimation);
++ return d->endRotation.isValid() ? d->endRotation.value() : qreal(0);
++ }
++
++ void QQuickPathAnimation::setEndRotation(qreal rotation)
++ {
++ Q_D(QQuickPathAnimation);
++ if (d->endRotation.isValid() && d->endRotation == rotation)
++ return;
++
++ d->endRotation = rotation;
++ emit endRotationChanged(d->endRotation);
++ }
++
++ QAbstractAnimationJob* QQuickPathAnimation::transition(QQuickStateActions &actions,
++ QQmlProperties &modified,
++ TransitionDirection direction,
++ QObject *defaultTarget)
++ {
++ Q_D(QQuickPathAnimation);
++
++ QQuickItem *target = d->target ? d->target : qobject_cast<QQuickItem*>(defaultTarget);
++
++ QQuickPathAnimationUpdater prevData;
++ bool havePrevData = false;
++ if (d->activeAnimations.contains(target)) {
++ havePrevData = true;
++ prevData = *d->activeAnimations[target]->pathUpdater();
++ }
++
++ for (auto it = d->activeAnimations.begin(); it != d->activeAnimations.end();) {
++ QQuickPathAnimationAnimator *anim = it.value();
++ if (anim->state() == QAbstractAnimationJob::Stopped) {
++ anim->clearTemplate();
++ it = d->activeAnimations.erase(it);
++ } else {
++ ++it;
++ }
++ }
++
++ QQuickPathAnimationUpdater *data = new QQuickPathAnimationUpdater();
++ QQuickPathAnimationAnimator *pa = new QQuickPathAnimationAnimator(d);
++
++ d->activeAnimations[target] = pa;
++
++ data->orientation = d->orientation;
++ data->anchorPoint = d->anchorPoint;
++ data->entryInterval = d->duration ? qreal(d->entryDuration) / d->duration : qreal(0);
++ data->exitInterval = d->duration ? qreal(d->exitDuration) / d->duration : qreal(0);
++ data->endRotation = d->endRotation;
++ data->reverse = direction == Backward ? true : false;
++ data->fromIsSourced = false;
++ data->fromIsDefined = (d->path && d->path->hasStartX() && d->path->hasStartY()) ? true : false;
++ data->toIsDefined = d->path ? true : false;
++ int origModifiedSize = modified.size();
++
++ for (int i = 0; i < actions.size(); ++i) {
++ QQuickStateAction &action = actions[i];
++ if (action.event)
++ continue;
++ if (action.specifiedObject == target && action.property.name() == QLatin1String("x")) {
++ data->toX = action.toValue.toReal();
++ modified << action.property;
++ action.fromValue = action.toValue;
++ }
++ if (action.specifiedObject == target && action.property.name() == QLatin1String("y")) {
++ data->toY = action.toValue.toReal();
++ modified << action.property;
++ action.fromValue = action.toValue;
++ }
++ }
++
++ if (target && d->path && (modified.size() > origModifiedSize || data->toIsDefined)) {
++ data->target = target;
++ data->path = d->path;
++ data->path->invalidateSequentialHistory();
++
++ if (havePrevData) {
++ // get the original start angle that was used (so we can exactly reverse).
++ data->startRotation = prevData.startRotation;
++
++ // treat interruptions specially, otherwise we end up with strange paths
++ if ((data->reverse || prevData.reverse) && prevData.currentV > 0 && prevData.currentV < 1) {
++ if (!data->fromIsDefined && !data->toIsDefined && !prevData.painterPath.isEmpty()) {
++ QPointF pathPos = QQuickPath::sequentialPointAt(prevData.painterPath, prevData.pathLength, prevData.attributePoints, prevData.prevBez, prevData.currentV);
++ if (!prevData.anchorPoint.isNull())
++ pathPos -= prevData.anchorPoint;
++ if (pathPos == data->target->position()) { //only treat as interruption if we interrupted ourself
++ data->painterPath = prevData.painterPath;
++ data->toIsDefined = data->fromIsDefined = data->fromIsSourced = true;
++ data->prevBez.isValid = false;
++ data->interruptStart = prevData.currentV;
++ data->startRotation = prevData.startRotation;
++ data->pathLength = prevData.pathLength;
++ data->attributePoints = prevData.attributePoints;
++ }
++ }
++ }
++ }
++ pa->setFromIsSourcedValue(&data->fromIsSourced);
++ pa->setAnimValue(data);
++ pa->setDuration(d->duration);
++ pa->setEasingCurve(d->easingCurve);
++ return initInstance(pa);
++ } else {
++ pa->setFromIsSourcedValue(nullptr);
++ pa->setAnimValue(nullptr);
++ delete pa;
++ delete data;
++ }
++ return nullptr;
++ }
++
++ void QQuickPathAnimationUpdater::setValue(qreal v)
++ {
++ v = qMin(qMax(v, (qreal)0.0), (qreal)1.0);;
++
++ if (interruptStart.isValid()) {
++ if (reverse)
++ v = 1 - v;
++ qreal end = reverse ? 0.0 : 1.0;
++ v = interruptStart + v * (end-interruptStart);
++ }
++ currentV = v;
++ bool atStart = ((reverse && v == 1.0) || (!reverse && v == 0.0));
++ if (!fromIsSourced && (!fromIsDefined || !toIsDefined)) {
++ qreal startX = reverse ? toX + anchorPoint.x() : target->x() + anchorPoint.x();
++ qreal startY = reverse ? toY + anchorPoint.y() : target->y() + anchorPoint.y();
++ qreal endX = reverse ? target->x() + anchorPoint.x() : toX + anchorPoint.x();
++ qreal endY = reverse ? target->y() + anchorPoint.y() : toY + anchorPoint.y();
++
++ prevBez.isValid = false;
++ painterPath = path->createPath(QPointF(startX, startY), QPointF(endX, endY), QStringList(), pathLength, attributePoints);
++ fromIsSourced = true;
++ }
++
++ qreal angle;
++ bool fixed = orientation == QQuickPathAnimation::Fixed;
++ QPointF currentPos = !painterPath.isEmpty() ? path->sequentialPointAt(painterPath, pathLength, attributePoints, prevBez, v, fixed ? nullptr : &angle) : path->sequentialPointAt(v, fixed ? nullptr : &angle);
++
++ //adjust position according to anchor point
++ if (!anchorPoint.isNull()) {
++ currentPos -= anchorPoint;
++ if (atStart) {
++ if (!anchorPoint.isNull() && !fixed)
++ target->setTransformOriginPoint(anchorPoint);
++ }
++ }
++
++ target->setPosition(currentPos);
++
++ //adjust angle according to orientation
++ if (!fixed) {
++ switch (orientation) {
++ case QQuickPathAnimation::RightFirst:
++ angle = -angle;
++ break;
++ case QQuickPathAnimation::TopFirst:
++ angle = -angle + 90;
++ break;
++ case QQuickPathAnimation::LeftFirst:
++ angle = -angle + 180;
++ break;
++ case QQuickPathAnimation::BottomFirst:
++ angle = -angle + 270;
++ break;
++ default:
++ angle = 0;
++ break;
++ }
++
++ if (atStart && !reverse) {
++ startRotation = target->rotation();
++
++ //shortest distance to correct orientation
++ qreal diff = angle - startRotation;
++ while (diff > 180.0) {
++ startRotation = startRotation.value() + 360.0;
++ diff -= 360.0;
++ }
++ while (diff < -180.0) {
++ startRotation = startRotation.value() - 360.0;
++ diff += 360.0;
++ }
++ }
++
++ //smoothly transition to the desired orientation
++ //TODO: shortest distance calculations
++ if (startRotation.isValid()) {
++ if (reverse && v == 0.0)
++ angle = startRotation;
++ else if (v < entryInterval)
++ angle = angle * v / entryInterval + startRotation * (entryInterval - v) / entryInterval;
++ }
++ if (endRotation.isValid()) {
++ qreal exitStart = 1 - entryInterval;
++ if (!reverse && v == 1.0)
++ angle = endRotation;
++ else if (v > exitStart)
++ angle = endRotation * (v - exitStart) / exitInterval + angle * (exitInterval - (v - exitStart)) / exitInterval;
++ }
++ target->setRotation(angle);
++ }
++
++ /*
++ NOTE: we don't always reset the transform origin, as it can cause a
++ visual jump if ending on an angle. This means that in some cases
++ (anchor point and orientation both specified, and ending at an angle)
++ the transform origin will always be set after running the path animation.
++ */
++ if ((reverse && v == 0.0) || (!reverse && v == 1.0)) {
++ if (!anchorPoint.isNull() && !fixed && qFuzzyIsNull(angle))
++ target->setTransformOriginPoint(QPointF());
++ }
++ }
++
++ QQuickPathAnimationAnimator::QQuickPathAnimationAnimator(QQuickPathAnimationPrivate *priv)
++ : animationTemplate(priv)
++ {
++ }
++
++ QQuickPathAnimationAnimator::~QQuickPathAnimationAnimator()
++ {
++ if (animationTemplate && pathUpdater()) {
++ auto it = animationTemplate->activeAnimations.constFind(pathUpdater()->target);
++ if (it != animationTemplate->activeAnimations.cend() && it.value() == this)
++ animationTemplate->activeAnimations.erase(it);
++ }
++ }
++
++ #endif // quick_path
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickitemanimation_p.cpp"