1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickspringanimation_p.h"
41
42#include "qquickanimation_p_p.h"
43#include <private/qqmlproperty_p.h>
44#include "private/qcontinuinganimationgroupjob_p.h"
45
46#include <QtCore/qdebug.h>
47
48#include <private/qobject_p.h>
49
50#include <cmath>
51
52#define DELAY_STOP_TIMER_INTERVAL 32
53
54QT_BEGIN_NAMESPACE
55
56class QQuickSpringAnimationPrivate;
57class Q_AUTOTEST_EXPORT QSpringAnimation : public QAbstractAnimationJob
58{
59 Q_DISABLE_COPY(QSpringAnimation)
60public:
61 QSpringAnimation(QQuickSpringAnimationPrivate * = nullptr);
62
63 ~QSpringAnimation();
64 int duration() const override;
65 void restart();
66 void init();
67
68 qreal currentValue;
69 qreal to;
70 qreal velocity;
71 int startTime;
72 int dura;
73 int lastTime;
74 int stopTime;
75 enum Mode {
76 Track,
77 Velocity,
78 Spring
79 };
80 Mode mode;
81 QQmlProperty target;
82
83 qreal velocityms;
84 qreal maxVelocity;
85 qreal mass;
86 qreal spring;
87 qreal damping;
88 qreal epsilon;
89 qreal modulus;
90
91 bool useMass : 1;
92 bool haveModulus : 1;
93 bool skipUpdate : 1;
94 typedef QHash<QQmlProperty, QSpringAnimation*> ActiveAnimationHash;
95 typedef ActiveAnimationHash::Iterator ActiveAnimationHashIt;
96
97 void clearTemplate() { animationTemplate = nullptr; }
98
99protected:
100 void updateCurrentTime(int time) override;
101 void updateState(QAbstractAnimationJob::State, QAbstractAnimationJob::State) override;
102 void debugAnimation(QDebug d) const override;
103
104private:
105 QQuickSpringAnimationPrivate *animationTemplate;
106};
107
108class QQuickSpringAnimationPrivate : public QQuickPropertyAnimationPrivate
109{
110 Q_DECLARE_PUBLIC(QQuickSpringAnimation)
111public:
112 QQuickSpringAnimationPrivate()
113 : QQuickPropertyAnimationPrivate()
114 , velocityms(0)
115 , maxVelocity(0)
116 , mass(1.0)
117 , spring(0.)
118 , damping(0.)
119 , epsilon(0.01)
120 , modulus(0.0)
121 , useMass(false)
122 , haveModulus(false)
123 , mode(QSpringAnimation::Track)
124 { elapsed.start(); }
125
126 void updateMode();
127 qreal velocityms;
128 qreal maxVelocity;
129 qreal mass;
130 qreal spring;
131 qreal damping;
132 qreal epsilon;
133 qreal modulus;
134
135 bool useMass : 1;
136 bool haveModulus : 1;
137 QSpringAnimation::Mode mode;
138
139 QSpringAnimation::ActiveAnimationHash activeAnimations;
140 QElapsedTimer elapsed;
141};
142
143QSpringAnimation::QSpringAnimation(QQuickSpringAnimationPrivate *priv)
144 : QAbstractAnimationJob()
145 , currentValue(0)
146 , to(0)
147 , velocity(0)
148 , startTime(0)
149 , dura(0)
150 , lastTime(0)
151 , stopTime(-1)
152 , mode(Track)
153 , velocityms(0)
154 , maxVelocity(0)
155 , mass(1.0)
156 , spring(0.)
157 , damping(0.)
158 , epsilon(0.01)
159 , modulus(0.0)
160 , useMass(false)
161 , haveModulus(false)
162 , skipUpdate(false)
163 , animationTemplate(priv)
164{
165}
166
167QSpringAnimation::~QSpringAnimation()
168{
169 if (animationTemplate) {
170 if (target.object()) {
171 ActiveAnimationHashIt it = animationTemplate->activeAnimations.find(target);
172 if (it != animationTemplate->activeAnimations.end() && it.value() == this)
173 animationTemplate->activeAnimations.erase(it);
174 } else {
175 //target is no longer valid, need to search linearly
176 for (ActiveAnimationHashIt it = animationTemplate->activeAnimations.begin(); it != animationTemplate->activeAnimations.end(); ++it) {
177 if (it.value() == this) {
178 animationTemplate->activeAnimations.erase(it);
179 break;
180 }
181 }
182 }
183 }
184}
185
186int QSpringAnimation::duration() const
187{
188 return -1;
189}
190
191void QSpringAnimation::restart()
192{
193 if (isRunning() || (stopTime != -1 && (animationTemplate->elapsed.elapsed() - stopTime) < DELAY_STOP_TIMER_INTERVAL)) {
194 skipUpdate = true;
195 init();
196 } else {
197 skipUpdate = false;
198 //init() will be triggered when group starts
199 }
200}
201
202void QSpringAnimation::init()
203{
204 lastTime = startTime = 0;
205 stopTime = -1;
206}
207
208void QSpringAnimation::updateCurrentTime(int time)
209{
210 if (skipUpdate) {
211 skipUpdate = false;
212 return;
213 }
214
215 if (mode == Track) {
216 stop();
217 return;
218 }
219
220 int elapsed = time - lastTime;
221
222 if (!elapsed)
223 return;
224
225 int count = elapsed / 16;
226
227 if (mode == Spring) {
228 if (elapsed < 16) // capped at 62fps.
229 return;
230 lastTime = time - (elapsed - count * 16);
231 } else {
232 lastTime = time;
233 }
234
235 qreal srcVal = to;
236
237 bool stopped = false;
238
239 if (haveModulus) {
240 currentValue = fmod(currentValue, modulus);
241 srcVal = fmod(srcVal, modulus);
242 }
243 if (mode == Spring) {
244 // Real men solve the spring DEs using RK4.
245 // We'll do something much simpler which gives a result that looks fine.
246 for (int i = 0; i < count; ++i) {
247 qreal diff = srcVal - currentValue;
248 if (haveModulus && qAbs(diff) > modulus / 2) {
249 if (diff < 0)
250 diff += modulus;
251 else
252 diff -= modulus;
253 }
254 if (useMass)
255 velocity = velocity + (spring * diff - damping * velocity) / mass;
256 else
257 velocity = velocity + spring * diff - damping * velocity;
258 if (maxVelocity > 0.) {
259 // limit velocity
260 if (velocity > maxVelocity)
261 velocity = maxVelocity;
262 else if (velocity < -maxVelocity)
263 velocity = -maxVelocity;
264 }
265 currentValue += velocity * 16.0 / 1000.0;
266 if (haveModulus) {
267 currentValue = fmod(currentValue, modulus);
268 if (currentValue < 0.0)
269 currentValue += modulus;
270 }
271 }
272 if (qAbs(velocity) < epsilon && qAbs(srcVal - currentValue) < epsilon) {
273 velocity = 0.0;
274 currentValue = srcVal;
275 stopped = true;
276 }
277 } else {
278 qreal moveBy = elapsed * velocityms;
279 qreal diff = srcVal - currentValue;
280 if (haveModulus && qAbs(diff) > modulus / 2) {
281 if (diff < 0)
282 diff += modulus;
283 else
284 diff -= modulus;
285 }
286 if (diff > 0) {
287 currentValue += moveBy;
288 if (haveModulus)
289 currentValue = std::fmod(currentValue, modulus);
290 } else {
291 currentValue -= moveBy;
292 if (haveModulus && currentValue < 0.0)
293 currentValue = std::fmod(currentValue, modulus) + modulus;
294 }
295 if (lastTime - startTime >= dura) {
296 currentValue = to;
297 stopped = true;
298 }
299 }
300
301 qreal old_to = to;
302
303 QQmlPropertyPrivate::write(target, currentValue,
304 QQmlPropertyData::BypassInterceptor |
305 QQmlPropertyData::DontRemoveBinding);
306
307 if (stopped && old_to == to) { // do not stop if we got restarted
308 if (animationTemplate)
309 stopTime = animationTemplate->elapsed.elapsed();
310 stop();
311 }
312}
313
314void QSpringAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/)
315{
316 if (newState == QAbstractAnimationJob::Running)
317 init();
318}
319
320void QSpringAnimation::debugAnimation(QDebug d) const
321{
322 d << "SpringAnimationJob(" << hex << (const void *) this << dec << ")" << "velocity:" << maxVelocity
323 << "spring:" << spring << "damping:" << damping << "epsilon:" << epsilon << "modulus:" << modulus
324 << "mass:" << mass << "target:" << target.object() << "property:" << target.name()
325 << "to:" << to << "current velocity:" << velocity;
326}
327
328
329void QQuickSpringAnimationPrivate::updateMode()
330{
331 if (spring == 0. && maxVelocity == 0.)
332 mode = QSpringAnimation::Track;
333 else if (spring > 0.)
334 mode = QSpringAnimation::Spring;
335 else {
336 mode = QSpringAnimation::Velocity;
337 for (QSpringAnimation::ActiveAnimationHashIt it = activeAnimations.begin(), end = activeAnimations.end(); it != end; ++it) {
338 QSpringAnimation *animation = *it;
339 animation->startTime = animation->lastTime;
340 qreal dist = qAbs(animation->currentValue - animation->to);
341 if (haveModulus && dist > modulus / 2)
342 dist = modulus - fmod(dist, modulus);
343 animation->dura = dist / velocityms;
344 }
345 }
346}
347
348/*!
349 \qmltype SpringAnimation
350 \instantiates QQuickSpringAnimation
351 \inqmlmodule QtQuick
352 \ingroup qtquick-transitions-animations
353 \inherits NumberAnimation
354
355 \brief Allows a property to track a value in a spring-like motion.
356
357 SpringAnimation mimics the oscillatory behavior of a spring, with the appropriate \l spring constant to
358 control the acceleration and the \l damping to control how quickly the effect dies away.
359
360 You can also limit the maximum \l velocity of the animation.
361
362 The following \l Rectangle moves to the position of the mouse using a
363 SpringAnimation when the mouse is clicked. The use of the \l Behavior
364 on the \c x and \c y values indicates that whenever these values are
365 changed, a SpringAnimation should be applied.
366
367 \snippet qml/springanimation.qml 0
368
369 Like any other animation type, a SpringAnimation can be applied in a
370 number of ways, including transitions, behaviors and property value
371 sources. The \l {Animation and Transitions in Qt Quick} documentation shows a
372 variety of methods for creating animations.
373
374 \sa SmoothedAnimation, {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation}, {Qt Quick Demo - Clocks}
375*/
376
377QQuickSpringAnimation::QQuickSpringAnimation(QObject *parent)
378: QQuickNumberAnimation(*(new QQuickSpringAnimationPrivate),parent)
379{
380}
381
382QQuickSpringAnimation::~QQuickSpringAnimation()
383{
384 Q_D(QQuickSpringAnimation);
385 for (QSpringAnimation::ActiveAnimationHashIt it = d->activeAnimations.begin(), end = d->activeAnimations.end(); it != end; ++it)
386 it.value()->clearTemplate();
387}
388
389/*!
390 \qmlproperty real QtQuick::SpringAnimation::velocity
391
392 This property holds the maximum velocity allowed when tracking the source.
393
394 The default value is 0 (no maximum velocity).
395*/
396
397qreal QQuickSpringAnimation::velocity() const
398{
399 Q_D(const QQuickSpringAnimation);
400 return d->maxVelocity;
401}
402
403void QQuickSpringAnimation::setVelocity(qreal velocity)
404{
405 Q_D(QQuickSpringAnimation);
406 d->maxVelocity = velocity;
407 d->velocityms = velocity / 1000.0;
408 d->updateMode();
409}
410
411/*!
412 \qmlproperty real QtQuick::SpringAnimation::spring
413
414 This property describes how strongly the target is pulled towards the
415 source. The default value is 0 (that is, the spring-like motion is disabled).
416
417 The useful value range is 0 - 5.0.
418
419 When this property is set and the \l velocity value is greater than 0,
420 the \l velocity limits the maximum speed.
421*/
422qreal QQuickSpringAnimation::spring() const
423{
424 Q_D(const QQuickSpringAnimation);
425 return d->spring;
426}
427
428void QQuickSpringAnimation::setSpring(qreal spring)
429{
430 Q_D(QQuickSpringAnimation);
431 d->spring = spring;
432 d->updateMode();
433}
434
435/*!
436 \qmlproperty real QtQuick::SpringAnimation::damping
437 This property holds the spring damping value.
438
439 This value describes how quickly the spring-like motion comes to rest.
440 The default value is 0.
441
442 The useful value range is 0 - 1.0. The lower the value, the faster it
443 comes to rest.
444*/
445qreal QQuickSpringAnimation::damping() const
446{
447 Q_D(const QQuickSpringAnimation);
448 return d->damping;
449}
450
451void QQuickSpringAnimation::setDamping(qreal damping)
452{
453 Q_D(QQuickSpringAnimation);
454 if (damping > 1.)
455 damping = 1.;
456
457 d->damping = damping;
458}
459
460
461/*!
462 \qmlproperty real QtQuick::SpringAnimation::epsilon
463 This property holds the spring epsilon.
464
465 The epsilon is the rate and amount of change in the value which is close enough
466 to 0 to be considered equal to zero. This will depend on the usage of the value.
467 For pixel positions, 0.25 would suffice. For scale, 0.005 will suffice.
468
469 The default is 0.01. Tuning this value can provide small performance improvements.
470*/
471qreal QQuickSpringAnimation::epsilon() const
472{
473 Q_D(const QQuickSpringAnimation);
474 return d->epsilon;
475}
476
477void QQuickSpringAnimation::setEpsilon(qreal epsilon)
478{
479 Q_D(QQuickSpringAnimation);
480 d->epsilon = epsilon;
481}
482
483/*!
484 \qmlproperty real QtQuick::SpringAnimation::modulus
485 This property holds the modulus value. The default value is 0.
486
487 Setting a \a modulus forces the target value to "wrap around" at the modulus.
488 For example, setting the modulus to 360 will cause a value of 370 to wrap around to 10.
489*/
490qreal QQuickSpringAnimation::modulus() const
491{
492 Q_D(const QQuickSpringAnimation);
493 return d->modulus;
494}
495
496void QQuickSpringAnimation::setModulus(qreal modulus)
497{
498 Q_D(QQuickSpringAnimation);
499 if (d->modulus != modulus) {
500 d->haveModulus = modulus != 0.0;
501 d->modulus = modulus;
502 d->updateMode();
503 emit modulusChanged();
504 }
505}
506
507/*!
508 \qmlproperty real QtQuick::SpringAnimation::mass
509 This property holds the "mass" of the property being moved.
510
511 The value is 1.0 by default.
512
513 A greater mass causes slower movement and a greater spring-like
514 motion when an item comes to rest.
515*/
516qreal QQuickSpringAnimation::mass() const
517{
518 Q_D(const QQuickSpringAnimation);
519 return d->mass;
520}
521
522void QQuickSpringAnimation::setMass(qreal mass)
523{
524 Q_D(QQuickSpringAnimation);
525 if (d->mass != mass && mass > 0.0) {
526 d->useMass = mass != 1.0;
527 d->mass = mass;
528 emit massChanged();
529 }
530}
531
532QAbstractAnimationJob* QQuickSpringAnimation::transition(QQuickStateActions &actions,
533 QQmlProperties &modified,
534 TransitionDirection direction,
535 QObject *defaultTarget)
536{
537 Q_D(QQuickSpringAnimation);
538 Q_UNUSED(direction);
539
540 QContinuingAnimationGroupJob *wrapperGroup = new QContinuingAnimationGroupJob();
541
542 QQuickStateActions dataActions = QQuickNumberAnimation::createTransitionActions(actions, modified, defaultTarget);
543 if (!dataActions.isEmpty()) {
544 QSet<QAbstractAnimationJob*> anims;
545 for (int i = 0; i < dataActions.size(); ++i) {
546 QSpringAnimation *animation;
547 bool needsRestart = false;
548 const QQmlProperty &property = dataActions.at(i).property;
549 if (d->activeAnimations.contains(property)) {
550 animation = d->activeAnimations[property];
551 needsRestart = true;
552 } else {
553 animation = new QSpringAnimation(d);
554 d->activeAnimations.insert(property, animation);
555 animation->target = property;
556 }
557 wrapperGroup->appendAnimation(initInstance(animation));
558
559 animation->to = dataActions.at(i).toValue.toReal();
560 animation->startTime = 0;
561 animation->velocityms = d->velocityms;
562 animation->mass = d->mass;
563 animation->spring = d->spring;
564 animation->damping = d->damping;
565 animation->epsilon = d->epsilon;
566 animation->modulus = d->modulus;
567 animation->useMass = d->useMass;
568 animation->haveModulus = d->haveModulus;
569 animation->mode = d->mode;
570 animation->dura = -1;
571 animation->maxVelocity = d->maxVelocity;
572
573 if (d->fromIsDefined)
574 animation->currentValue = dataActions.at(i).fromValue.toReal();
575 else
576 animation->currentValue = property.read().toReal();
577 if (animation->mode == QSpringAnimation::Velocity) {
578 qreal dist = qAbs(animation->currentValue - animation->to);
579 if (d->haveModulus && dist > d->modulus / 2)
580 dist = d->modulus - fmod(dist, d->modulus);
581 animation->dura = dist / animation->velocityms;
582 }
583
584 if (needsRestart)
585 animation->restart();
586 anims.insert(animation);
587 }
588 const auto copy = d->activeAnimations;
589 for (QSpringAnimation *anim : copy) {
590 if (!anims.contains(anim)) {
591 anim->clearTemplate();
592 d->activeAnimations.remove(anim->target);
593 }
594 }
595 }
596 return wrapperGroup;
597}
598
599QT_END_NAMESPACE
600
601#include "moc_qquickspringanimation_p.cpp"
602