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 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | class QQuickSpringAnimationPrivate; |
57 | class Q_AUTOTEST_EXPORT QSpringAnimation : public QAbstractAnimationJob |
58 | { |
59 | Q_DISABLE_COPY(QSpringAnimation) |
60 | public: |
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 | |
99 | protected: |
100 | void updateCurrentTime(int time) override; |
101 | void updateState(QAbstractAnimationJob::State, QAbstractAnimationJob::State) override; |
102 | void debugAnimation(QDebug d) const override; |
103 | |
104 | private: |
105 | QQuickSpringAnimationPrivate *animationTemplate; |
106 | }; |
107 | |
108 | class QQuickSpringAnimationPrivate : public QQuickPropertyAnimationPrivate |
109 | { |
110 | Q_DECLARE_PUBLIC(QQuickSpringAnimation) |
111 | public: |
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 | |
143 | QSpringAnimation::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 | |
167 | QSpringAnimation::~QSpringAnimation() |
168 | { |
169 | if (animationTemplate) { |
170 | if (target.object()) { |
171 | ActiveAnimationHashIt it = animationTemplate->activeAnimations.find(key: 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 | |
186 | int QSpringAnimation::duration() const |
187 | { |
188 | return -1; |
189 | } |
190 | |
191 | void 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 | |
202 | void QSpringAnimation::init() |
203 | { |
204 | lastTime = startTime = 0; |
205 | stopTime = -1; |
206 | } |
207 | |
208 | void 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(x: currentValue, y: modulus); |
241 | srcVal = fmod(x: srcVal, y: 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(t: 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(x: currentValue, y: modulus); |
268 | if (currentValue < 0.0) |
269 | currentValue += modulus; |
270 | } |
271 | } |
272 | if (qAbs(t: velocity) < epsilon && qAbs(t: 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(t: 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(x: currentValue, y: modulus); |
290 | } else { |
291 | currentValue -= moveBy; |
292 | if (haveModulus && currentValue < 0.0) |
293 | currentValue = std::fmod(x: currentValue, y: 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(that: 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 | |
314 | void QSpringAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/) |
315 | { |
316 | if (newState == QAbstractAnimationJob::Running) |
317 | init(); |
318 | } |
319 | |
320 | void QSpringAnimation::debugAnimation(QDebug d) const |
321 | { |
322 | d << "SpringAnimationJob(" << Qt::hex << (const void *) this << Qt::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 | |
329 | void 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(t: animation->currentValue - animation->to); |
341 | if (haveModulus && dist > modulus / 2) |
342 | dist = modulus - fmod(x: dist, y: 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 | |
377 | QQuickSpringAnimation::QQuickSpringAnimation(QObject *parent) |
378 | : QQuickNumberAnimation(*(new QQuickSpringAnimationPrivate),parent) |
379 | { |
380 | } |
381 | |
382 | QQuickSpringAnimation::~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 | |
397 | qreal QQuickSpringAnimation::velocity() const |
398 | { |
399 | Q_D(const QQuickSpringAnimation); |
400 | return d->maxVelocity; |
401 | } |
402 | |
403 | void 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 | */ |
422 | qreal QQuickSpringAnimation::spring() const |
423 | { |
424 | Q_D(const QQuickSpringAnimation); |
425 | return d->spring; |
426 | } |
427 | |
428 | void 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 | */ |
445 | qreal QQuickSpringAnimation::damping() const |
446 | { |
447 | Q_D(const QQuickSpringAnimation); |
448 | return d->damping; |
449 | } |
450 | |
451 | void 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 | */ |
471 | qreal QQuickSpringAnimation::epsilon() const |
472 | { |
473 | Q_D(const QQuickSpringAnimation); |
474 | return d->epsilon; |
475 | } |
476 | |
477 | void 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 | */ |
490 | qreal QQuickSpringAnimation::modulus() const |
491 | { |
492 | Q_D(const QQuickSpringAnimation); |
493 | return d->modulus; |
494 | } |
495 | |
496 | void 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 | */ |
516 | qreal QQuickSpringAnimation::mass() const |
517 | { |
518 | Q_D(const QQuickSpringAnimation); |
519 | return d->mass; |
520 | } |
521 | |
522 | void 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 | |
532 | QAbstractAnimationJob* 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(key: property)) { |
550 | animation = d->activeAnimations[property]; |
551 | needsRestart = true; |
552 | } else { |
553 | animation = new QSpringAnimation(d); |
554 | d->activeAnimations.insert(key: property, value: animation); |
555 | animation->target = property; |
556 | } |
557 | wrapperGroup->appendAnimation(animation: 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(t: animation->currentValue - animation->to); |
579 | if (d->haveModulus && dist > d->modulus / 2) |
580 | dist = d->modulus - fmod(x: dist, y: d->modulus); |
581 | animation->dura = dist / animation->velocityms; |
582 | } |
583 | |
584 | if (needsRestart) |
585 | animation->restart(); |
586 | anims.insert(value: animation); |
587 | } |
588 | const auto copy = d->activeAnimations; |
589 | for (QSpringAnimation *anim : copy) { |
590 | if (!anims.contains(value: anim)) { |
591 | anim->clearTemplate(); |
592 | d->activeAnimations.remove(key: anim->target); |
593 | } |
594 | } |
595 | } |
596 | return wrapperGroup; |
597 | } |
598 | |
599 | QT_END_NAMESPACE |
600 | |
601 | #include "moc_qquickspringanimation_p.cpp" |
602 | |