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 "qquickspringanimation_p.h"
++
++ #include "qquickanimation_p_p.h"
++ #include <private/qqmlproperty_p.h>
++ #include "private/qcontinuinganimationgroupjob_p.h"
++
++ #include <QtCore/qdebug.h>
++
++ #include <private/qobject_p.h>
++
++ #include <cmath>
++
++ #define DELAY_STOP_TIMER_INTERVAL 32
++
++ QT_BEGIN_NAMESPACE
++
++ class QQuickSpringAnimationPrivate;
++ class Q_AUTOTEST_EXPORT QSpringAnimation : public QAbstractAnimationJob
++ {
++ Q_DISABLE_COPY(QSpringAnimation)
++ public:
++ QSpringAnimation(QQuickSpringAnimationPrivate * = nullptr);
++
++ ~QSpringAnimation();
++ int duration() const override;
++ void restart();
++ void init();
++
++ qreal currentValue;
++ qreal to;
++ qreal velocity;
++ int startTime;
++ int dura;
++ int lastTime;
++ int stopTime;
++ enum Mode {
++ Track,
++ Velocity,
++ Spring
++ };
++ Mode mode;
++ QQmlProperty target;
++
++ qreal velocityms;
++ qreal maxVelocity;
++ qreal mass;
++ qreal spring;
++ qreal damping;
++ qreal epsilon;
++ qreal modulus;
++
++ bool useMass : 1;
++ bool haveModulus : 1;
++ bool skipUpdate : 1;
++ typedef QHash<QQmlProperty, QSpringAnimation*> ActiveAnimationHash;
++ typedef ActiveAnimationHash::Iterator ActiveAnimationHashIt;
++
++ void clearTemplate() { animationTemplate = nullptr; }
++
++ protected:
++ void updateCurrentTime(int time) override;
++ void updateState(QAbstractAnimationJob::State, QAbstractAnimationJob::State) override;
++ void debugAnimation(QDebug d) const override;
++
++ private:
++ QQuickSpringAnimationPrivate *animationTemplate;
++ };
++
++ class QQuickSpringAnimationPrivate : public QQuickPropertyAnimationPrivate
++ {
++ Q_DECLARE_PUBLIC(QQuickSpringAnimation)
++ public:
++ QQuickSpringAnimationPrivate()
++ : QQuickPropertyAnimationPrivate()
++ , velocityms(0)
++ , maxVelocity(0)
++ , mass(1.0)
++ , spring(0.)
++ , damping(0.)
++ , epsilon(0.01)
++ , modulus(0.0)
++ , useMass(false)
++ , haveModulus(false)
++ , mode(QSpringAnimation::Track)
++ { elapsed.start(); }
++
++ void updateMode();
++ qreal velocityms;
++ qreal maxVelocity;
++ qreal mass;
++ qreal spring;
++ qreal damping;
++ qreal epsilon;
++ qreal modulus;
++
++ bool useMass : 1;
++ bool haveModulus : 1;
++ QSpringAnimation::Mode mode;
++
++ QSpringAnimation::ActiveAnimationHash activeAnimations;
++ QElapsedTimer elapsed;
++ };
++
++ QSpringAnimation::QSpringAnimation(QQuickSpringAnimationPrivate *priv)
++ : QAbstractAnimationJob()
++ , currentValue(0)
++ , to(0)
++ , velocity(0)
++ , startTime(0)
++ , dura(0)
++ , lastTime(0)
++ , stopTime(-1)
++ , mode(Track)
++ , velocityms(0)
++ , maxVelocity(0)
++ , mass(1.0)
++ , spring(0.)
++ , damping(0.)
++ , epsilon(0.01)
++ , modulus(0.0)
++ , useMass(false)
++ , haveModulus(false)
++ , skipUpdate(false)
++ , animationTemplate(priv)
++ {
++ }
++
++ QSpringAnimation::~QSpringAnimation()
++ {
++ if (animationTemplate) {
++ if (target.object()) {
++ auto it = animationTemplate->activeAnimations.constFind(target);
++ if (it != animationTemplate->activeAnimations.cend() && it.value() == this)
++ animationTemplate->activeAnimations.erase(it);
++ } else {
++ //target is no longer valid, need to search linearly
++ for (auto it = animationTemplate->activeAnimations.cbegin(); it != animationTemplate->activeAnimations.cend(); ++it) {
++ if (it.value() == this) {
++ animationTemplate->activeAnimations.erase(it);
++ break;
++ }
++ }
++ }
++ }
++ }
++
++ int QSpringAnimation::duration() const
++ {
++ return -1;
++ }
++
++ void QSpringAnimation::restart()
++ {
++ if (isRunning() || (stopTime != -1 && (animationTemplate->elapsed.elapsed() - stopTime) < DELAY_STOP_TIMER_INTERVAL)) {
++ skipUpdate = true;
++ init();
++ } else {
++ skipUpdate = false;
++ //init() will be triggered when group starts
++ }
++ }
++
++ void QSpringAnimation::init()
++ {
++ lastTime = startTime = 0;
++ stopTime = -1;
++ }
++
++ void QSpringAnimation::updateCurrentTime(int time)
++ {
++ if (skipUpdate) {
++ skipUpdate = false;
++ return;
++ }
++
++ if (mode == Track) {
++ stop();
++ return;
++ }
++
++ int elapsed = time - lastTime;
++
++ if (!elapsed)
++ return;
++
++ int count = elapsed / 16;
++
++ if (mode == Spring) {
++ if (elapsed < 16) // capped at 62fps.
++ return;
++ lastTime = time - (elapsed - count * 16);
++ } else {
++ lastTime = time;
++ }
++
++ qreal srcVal = to;
++
++ bool stopped = false;
++
++ if (haveModulus) {
++ currentValue = fmod(currentValue, modulus);
++ srcVal = fmod(srcVal, modulus);
++ }
++ if (mode == Spring) {
++ // Real men solve the spring DEs using RK4.
++ // We'll do something much simpler which gives a result that looks fine.
++ for (int i = 0; i < count; ++i) {
++ qreal diff = srcVal - currentValue;
++ if (haveModulus && qAbs(diff) > modulus / 2) {
++ if (diff < 0)
++ diff += modulus;
++ else
++ diff -= modulus;
++ }
++ if (useMass)
++ velocity = velocity + (spring * diff - damping * velocity) / mass;
++ else
++ velocity = velocity + spring * diff - damping * velocity;
++ if (maxVelocity > 0.) {
++ // limit velocity
++ if (velocity > maxVelocity)
++ velocity = maxVelocity;
++ else if (velocity < -maxVelocity)
++ velocity = -maxVelocity;
++ }
++ currentValue += velocity * 16.0 / 1000.0;
++ if (haveModulus) {
++ currentValue = fmod(currentValue, modulus);
++ if (currentValue < 0.0)
++ currentValue += modulus;
++ }
++ }
++ if (qAbs(velocity) < epsilon && qAbs(srcVal - currentValue) < epsilon) {
++ velocity = 0.0;
++ currentValue = srcVal;
++ stopped = true;
++ }
++ } else {
++ qreal moveBy = elapsed * velocityms;
++ qreal diff = srcVal - currentValue;
++ if (haveModulus && qAbs(diff) > modulus / 2) {
++ if (diff < 0)
++ diff += modulus;
++ else
++ diff -= modulus;
++ }
++ if (diff > 0) {
++ currentValue += moveBy;
++ if (haveModulus)
++ currentValue = std::fmod(currentValue, modulus);
++ } else {
++ currentValue -= moveBy;
++ if (haveModulus && currentValue < 0.0)
++ currentValue = std::fmod(currentValue, modulus) + modulus;
++ }
++ if (lastTime - startTime >= dura) {
++ currentValue = to;
++ stopped = true;
++ }
++ }
++
++ qreal old_to = to;
++
++ QQmlPropertyPrivate::write(target, currentValue,
++ QQmlPropertyData::BypassInterceptor |
++ QQmlPropertyData::DontRemoveBinding);
++
++ if (stopped && old_to == to) { // do not stop if we got restarted
++ if (animationTemplate)
++ stopTime = animationTemplate->elapsed.elapsed();
++ stop();
++ }
++ }
++
++ void QSpringAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/)
++ {
++ if (newState == QAbstractAnimationJob::Running)
++ init();
++ }
++
++ void QSpringAnimation::debugAnimation(QDebug d) const
++ {
++ d << "SpringAnimationJob(" << Qt::hex << (const void *) this << Qt::dec << ")" << "velocity:" << maxVelocity
++ << "spring:" << spring << "damping:" << damping << "epsilon:" << epsilon << "modulus:" << modulus
++ << "mass:" << mass << "target:" << target.object() << "property:" << target.name()
++ << "to:" << to << "current velocity:" << velocity;
++ }
++
++
++ void QQuickSpringAnimationPrivate::updateMode()
++ {
++ if (spring == 0. && maxVelocity == 0.)
++ mode = QSpringAnimation::Track;
++ else if (spring > 0.)
++ mode = QSpringAnimation::Spring;
++ else {
++ mode = QSpringAnimation::Velocity;
++ for (QSpringAnimation::ActiveAnimationHashIt it = activeAnimations.begin(), end = activeAnimations.end(); it != end; ++it) {
++ QSpringAnimation *animation = *it;
++ animation->startTime = animation->lastTime;
++ qreal dist = qAbs(animation->currentValue - animation->to);
++ if (haveModulus && dist > modulus / 2)
++ dist = modulus - fmod(dist, modulus);
++ animation->dura = dist / velocityms;
++ }
++ }
++ }
++
++ /*!
++ \qmltype SpringAnimation
++ \nativetype QQuickSpringAnimation
++ \inqmlmodule QtQuick
++ \ingroup qtquick-transitions-animations
++ \inherits NumberAnimation
++
++ \brief Allows a property to track a value in a spring-like motion.
++
++ SpringAnimation mimics the oscillatory behavior of a spring, with the appropriate \l spring constant to
++ control the acceleration and the \l damping to control how quickly the effect dies away.
++
++ You can also limit the maximum \l velocity of the animation.
++
++ The following \l Rectangle moves to the position of the mouse using a
++ SpringAnimation when the mouse is clicked. The use of the \l Behavior
++ on the \c x and \c y values indicates that whenever these values are
++ changed, a SpringAnimation should be applied.
++
++ \snippet qml/springanimation.qml 0
++
++ Like any other animation type, a SpringAnimation can be applied in a
++ number of ways, including transitions, behaviors and property value
++ sources. The \l {Animation and Transitions in Qt Quick} documentation shows a
++ variety of methods for creating animations.
++
++ \sa SmoothedAnimation, {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation}, {Qt Quick Demo - Clocks}
++ */
++
++ QQuickSpringAnimation::QQuickSpringAnimation(QObject *parent)
++ : QQuickNumberAnimation(*(new QQuickSpringAnimationPrivate),parent)
++ {
++ }
++
++ QQuickSpringAnimation::~QQuickSpringAnimation()
++ {
++ Q_D(QQuickSpringAnimation);
++ for (QSpringAnimation::ActiveAnimationHashIt it = d->activeAnimations.begin(), end = d->activeAnimations.end(); it != end; ++it)
++ it.value()->clearTemplate();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::SpringAnimation::velocity
++
++ This property holds the maximum velocity allowed when tracking the source.
++
++ The default value is 0 (no maximum velocity).
++ */
++
++ qreal QQuickSpringAnimation::velocity() const
++ {
++ Q_D(const QQuickSpringAnimation);
++ return d->maxVelocity;
++ }
++
++ void QQuickSpringAnimation::setVelocity(qreal velocity)
++ {
++ Q_D(QQuickSpringAnimation);
++ d->maxVelocity = velocity;
++ d->velocityms = velocity / 1000.0;
++ d->updateMode();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::SpringAnimation::spring
++
++ This property describes how strongly the target is pulled towards the
++ source. The default value is 0 (that is, the spring-like motion is disabled).
++
++ The useful value range is 0 - 5.0.
++
++ When this property is set and the \l velocity value is greater than 0,
++ the \l velocity limits the maximum speed.
++ */
++ qreal QQuickSpringAnimation::spring() const
++ {
++ Q_D(const QQuickSpringAnimation);
++ return d->spring;
++ }
++
++ void QQuickSpringAnimation::setSpring(qreal spring)
++ {
++ Q_D(QQuickSpringAnimation);
++ d->spring = spring;
++ d->updateMode();
++ }
++
++ /*!
++ \qmlproperty real QtQuick::SpringAnimation::damping
++ This property holds the spring damping value.
++
++ This value describes how quickly the spring-like motion comes to rest.
++ The default value is 0.
++
++ The useful value range is 0 - 1.0. The lower the value, the faster it
++ comes to rest.
++ */
++ qreal QQuickSpringAnimation::damping() const
++ {
++ Q_D(const QQuickSpringAnimation);
++ return d->damping;
++ }
++
++ void QQuickSpringAnimation::setDamping(qreal damping)
++ {
++ Q_D(QQuickSpringAnimation);
++ if (damping > 1.)
++ damping = 1.;
++
++ d->damping = damping;
++ }
++
++
++ /*!
++ \qmlproperty real QtQuick::SpringAnimation::epsilon
++ This property holds the spring epsilon.
++
++ The epsilon is the rate and amount of change in the value which is close enough
++ to 0 to be considered equal to zero. This will depend on the usage of the value.
++ For pixel positions, 0.25 would suffice. For scale, 0.005 will suffice.
++
++ The default is 0.01. Tuning this value can provide small performance improvements.
++ */
++ qreal QQuickSpringAnimation::epsilon() const
++ {
++ Q_D(const QQuickSpringAnimation);
++ return d->epsilon;
++ }
++
++ void QQuickSpringAnimation::setEpsilon(qreal epsilon)
++ {
++ Q_D(QQuickSpringAnimation);
++ d->epsilon = epsilon;
++ }
++
++ /*!
++ \qmlproperty real QtQuick::SpringAnimation::modulus
++ This property holds the modulus value. The default value is 0.
++
++ Setting a \a modulus forces the target value to "wrap around" at the modulus.
++ For example, setting the modulus to 360 will cause a value of 370 to wrap around to 10.
++ */
++ qreal QQuickSpringAnimation::modulus() const
++ {
++ Q_D(const QQuickSpringAnimation);
++ return d->modulus;
++ }
++
++ void QQuickSpringAnimation::setModulus(qreal modulus)
++ {
++ Q_D(QQuickSpringAnimation);
++ if (d->modulus != modulus) {
++ d->haveModulus = modulus != 0.0;
++ d->modulus = modulus;
++ d->updateMode();
++ emit modulusChanged();
++ }
++ }
++
++ /*!
++ \qmlproperty real QtQuick::SpringAnimation::mass
++ This property holds the "mass" of the property being moved.
++
++ The value is 1.0 by default.
++
++ A greater mass causes slower movement and a greater spring-like
++ motion when an item comes to rest.
++ */
++ qreal QQuickSpringAnimation::mass() const
++ {
++ Q_D(const QQuickSpringAnimation);
++ return d->mass;
++ }
++
++ void QQuickSpringAnimation::setMass(qreal mass)
++ {
++ Q_D(QQuickSpringAnimation);
++ if (d->mass != mass && mass > 0.0) {
++ d->useMass = mass != 1.0;
++ d->mass = mass;
++ emit massChanged();
++ }
++ }
++
++ QAbstractAnimationJob* QQuickSpringAnimation::transition(QQuickStateActions &actions,
++ QQmlProperties &modified,
++ TransitionDirection direction,
++ QObject *defaultTarget)
++ {
++ Q_D(QQuickSpringAnimation);
++ Q_UNUSED(direction);
++
++ QContinuingAnimationGroupJob *wrapperGroup = new QContinuingAnimationGroupJob();
++
++ QQuickStateActions dataActions = QQuickNumberAnimation::createTransitionActions(actions, modified, defaultTarget);
++ if (!dataActions.isEmpty()) {
++ QSet<QAbstractAnimationJob*> anims;
++ for (int i = 0; i < dataActions.size(); ++i) {
++ QSpringAnimation *animation;
++ bool needsRestart = false;
++ const QQmlProperty &property = dataActions.at(i).property;
++ if (d->activeAnimations.contains(property)) {
++ animation = d->activeAnimations[property];
++ needsRestart = true;
++ } else {
++ animation = new QSpringAnimation(d);
++ d->activeAnimations.insert(property, animation);
++ animation->target = property;
++ }
++ wrapperGroup->appendAnimation(initInstance(animation));
++
++ animation->to = dataActions.at(i).toValue.toReal();
++ animation->startTime = 0;
++ animation->velocityms = d->velocityms;
++ animation->mass = d->mass;
++ animation->spring = d->spring;
++ animation->damping = d->damping;
++ animation->epsilon = d->epsilon;
++ animation->modulus = d->modulus;
++ animation->useMass = d->useMass;
++ animation->haveModulus = d->haveModulus;
++ animation->mode = d->mode;
++ animation->dura = -1;
++ animation->maxVelocity = d->maxVelocity;
++
++ if (d->fromIsDefined)
++ animation->currentValue = dataActions.at(i).fromValue.toReal();
++ else
++ animation->currentValue = property.read().toReal();
++ if (animation->mode == QSpringAnimation::Velocity) {
++ qreal dist = qAbs(animation->currentValue - animation->to);
++ if (d->haveModulus && dist > d->modulus / 2)
++ dist = d->modulus - fmod(dist, d->modulus);
++ animation->dura = dist / animation->velocityms;
++ }
++
++ if (needsRestart)
++ animation->restart();
++ anims.insert(animation);
++ }
++ const auto copy = d->activeAnimations;
++ for (QSpringAnimation *anim : copy) {
++ if (!anims.contains(anim)) {
++ anim->clearTemplate();
++ d->activeAnimations.remove(anim->target);
++ }
++ }
++ }
++ return wrapperGroup;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickspringanimation_p.cpp"