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