| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #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()) { |
| ActiveAnimationHashIt it = animationTemplate->activeAnimations.find(target); |
| if (it != animationTemplate->activeAnimations.end() && it.value() == this) |
| animationTemplate->activeAnimations.erase(it); |
| } else { |
| //target is no longer valid, need to search linearly |
| for (ActiveAnimationHashIt it = animationTemplate->activeAnimations.begin(); it != animationTemplate->activeAnimations.end(); ++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 |
| \instantiates 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" |