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 | |
4 | #include "qquicksmoothedanimation_p.h" |
5 | #include "qquicksmoothedanimation_p_p.h" |
6 | |
7 | #include "qquickanimation_p_p.h" |
8 | #include "private/qcontinuinganimationgroupjob_p.h" |
9 | |
10 | #include <qmath.h> |
11 | #include <qqmlproperty.h> |
12 | #include <private/qqmlproperty_p.h> |
13 | |
14 | #include <private/qqmlglobal_p.h> |
15 | |
16 | #include <QtCore/qdebug.h> |
17 | |
18 | |
19 | #define DELAY_STOP_TIMER_INTERVAL 32 |
20 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | |
24 | QSmoothedAnimationTimer::QSmoothedAnimationTimer(QSmoothedAnimation *animation, QObject *parent) |
25 | : QTimer(parent) |
26 | , m_animation(animation) |
27 | { |
28 | connect(sender: this, SIGNAL(timeout()), receiver: this, SLOT(stopAnimation())); |
29 | } |
30 | |
31 | QSmoothedAnimationTimer::~QSmoothedAnimationTimer() |
32 | { |
33 | } |
34 | |
35 | void QSmoothedAnimationTimer::stopAnimation() |
36 | { |
37 | m_animation->stop(); |
38 | } |
39 | |
40 | QSmoothedAnimation::QSmoothedAnimation(QQuickSmoothedAnimationPrivate *priv) |
41 | : QAbstractAnimationJob(), to(0), velocity(200), userDuration(-1), maximumEasingTime(-1), |
42 | reversingMode(QQuickSmoothedAnimation::Eased), initialVelocity(0), |
43 | trackVelocity(0), initialValue(0), invert(false), finalDuration(-1), lastTime(0), |
44 | skipUpdate(false), delayedStopTimer(new QSmoothedAnimationTimer(this)), animationTemplate(priv) |
45 | { |
46 | delayedStopTimer->setInterval(DELAY_STOP_TIMER_INTERVAL); |
47 | delayedStopTimer->setSingleShot(true); |
48 | } |
49 | |
50 | QSmoothedAnimation::~QSmoothedAnimation() |
51 | { |
52 | delete delayedStopTimer; |
53 | if (animationTemplate) { |
54 | if (target.object()) { |
55 | auto it = animationTemplate->activeAnimations.constFind(key: target); |
56 | if (it != animationTemplate->activeAnimations.cend() && it.value() == this) |
57 | animationTemplate->activeAnimations.erase(it); |
58 | } else { |
59 | //target is no longer valid, need to search linearly |
60 | for (auto it = animationTemplate->activeAnimations.cbegin(); it != animationTemplate->activeAnimations.cend(); ++it) { |
61 | if (it.value() == this) { |
62 | animationTemplate->activeAnimations.erase(it); |
63 | break; |
64 | } |
65 | } |
66 | } |
67 | } |
68 | } |
69 | |
70 | void QSmoothedAnimation::restart() |
71 | { |
72 | initialVelocity = trackVelocity; |
73 | if (isRunning()) |
74 | init(); |
75 | else |
76 | start(); |
77 | } |
78 | |
79 | void QSmoothedAnimation::prepareForRestart() |
80 | { |
81 | initialVelocity = trackVelocity; |
82 | if (isRunning()) { |
83 | //we are joining a new wrapper group while running, our times need to be restarted |
84 | skipUpdate = true; |
85 | init(); |
86 | lastTime = 0; |
87 | } else { |
88 | skipUpdate = false; |
89 | //we'll be started when the group starts, which will force an init() |
90 | } |
91 | } |
92 | |
93 | void QSmoothedAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/) |
94 | { |
95 | if (newState == QAbstractAnimationJob::Running) |
96 | init(); |
97 | } |
98 | |
99 | void QSmoothedAnimation::delayedStop() |
100 | { |
101 | if (!delayedStopTimer->isActive()) |
102 | delayedStopTimer->start(); |
103 | } |
104 | |
105 | int QSmoothedAnimation::duration() const |
106 | { |
107 | return -1; |
108 | } |
109 | |
110 | bool QSmoothedAnimation::recalc() |
111 | { |
112 | s = to - initialValue; |
113 | vi = initialVelocity; |
114 | |
115 | s = (invert? -1.0: 1.0) * s; |
116 | |
117 | if (userDuration >= 0 && velocity > 0) { |
118 | tf = s / velocity; |
119 | if (tf > (userDuration / 1000.)) tf = (userDuration / 1000.); |
120 | } else if (userDuration >= 0) { |
121 | tf = userDuration / 1000.; |
122 | } else if (velocity > 0) { |
123 | tf = s / velocity; |
124 | } else { |
125 | return false; |
126 | } |
127 | |
128 | finalDuration = qCeil(v: tf * 1000.0); |
129 | |
130 | if (maximumEasingTime == 0) { |
131 | a = 0; |
132 | d = 0; |
133 | tp = 0; |
134 | td = tf; |
135 | vp = velocity; |
136 | sp = 0; |
137 | sd = s; |
138 | } else if (maximumEasingTime != -1 && tf > (maximumEasingTime / 1000.)) { |
139 | qreal met = maximumEasingTime / 1000.; |
140 | /* tp| |td |
141 | * vp_ _______ |
142 | * / \ |
143 | * vi_ / \ |
144 | * \ |
145 | * \ _ 0 |
146 | * |ta| |ta| |
147 | */ |
148 | qreal ta = met / 2.; |
149 | a = (s - (vi * tf - 0.5 * vi * ta)) / (tf * ta - ta * ta); |
150 | |
151 | vp = vi + a * ta; |
152 | d = vp / ta; |
153 | tp = ta; |
154 | sp = vi * ta + 0.5 * a * tp * tp; |
155 | sd = sp + vp * (tf - 2 * ta); |
156 | td = tf - ta; |
157 | } else { |
158 | qreal c1 = 0.25 * tf * tf; |
159 | qreal c2 = 0.5 * vi * tf - s; |
160 | qreal c3 = -0.25 * vi * vi; |
161 | |
162 | qreal a1 = (-c2 + qSqrt(v: c2 * c2 - 4 * c1 * c3)) / (2. * c1); |
163 | |
164 | qreal tp1 = 0.5 * tf - 0.5 * vi / a1; |
165 | qreal vp1 = a1 * tp1 + vi; |
166 | |
167 | qreal sp1 = 0.5 * a1 * tp1 * tp1 + vi * tp1; |
168 | |
169 | a = a1; |
170 | d = a1; |
171 | tp = tp1; |
172 | td = tp1; |
173 | vp = vp1; |
174 | sp = sp1; |
175 | sd = sp1; |
176 | } |
177 | return true; |
178 | } |
179 | |
180 | qreal QSmoothedAnimation::easeFollow(qreal time_seconds) |
181 | { |
182 | qreal value; |
183 | if (time_seconds < tp) { |
184 | trackVelocity = vi + time_seconds * a; |
185 | value = 0.5 * a * time_seconds * time_seconds + vi * time_seconds; |
186 | } else if (time_seconds < td) { |
187 | time_seconds -= tp; |
188 | trackVelocity = vp; |
189 | value = sp + time_seconds * vp; |
190 | } else if (time_seconds < tf) { |
191 | time_seconds -= td; |
192 | trackVelocity = vp - time_seconds * a; |
193 | value = sd - 0.5 * d * time_seconds * time_seconds + vp * time_seconds; |
194 | } else { |
195 | trackVelocity = 0; |
196 | value = s; |
197 | delayedStop(); |
198 | } |
199 | |
200 | // to normalize 's' between [0..1], divide 'value' by 's' |
201 | return value; |
202 | } |
203 | |
204 | void QSmoothedAnimation::updateCurrentTime(int t) |
205 | { |
206 | if (skipUpdate) { |
207 | skipUpdate = false; |
208 | return; |
209 | } |
210 | |
211 | if (!isRunning() && !isPaused()) // This can happen if init() stops the animation in some cases |
212 | return; |
213 | |
214 | qreal time_seconds = qreal(t - lastTime) / 1000.; |
215 | |
216 | qreal value = easeFollow(time_seconds); |
217 | value *= (invert? -1.0: 1.0); |
218 | QQmlPropertyPrivate::write(that: target, initialValue + value, |
219 | QQmlPropertyData::BypassInterceptor |
220 | | QQmlPropertyData::DontRemoveBinding); |
221 | } |
222 | |
223 | void QSmoothedAnimation::init() |
224 | { |
225 | if (velocity == 0) { |
226 | stop(); |
227 | return; |
228 | } |
229 | |
230 | if (delayedStopTimer->isActive()) |
231 | delayedStopTimer->stop(); |
232 | |
233 | initialValue = target.read().toReal(); |
234 | lastTime = this->currentTime(); |
235 | |
236 | if (to == initialValue) { |
237 | stop(); |
238 | return; |
239 | } |
240 | |
241 | bool hasReversed = trackVelocity != 0. && |
242 | ((!invert) == ((initialValue - to) > 0)); |
243 | |
244 | if (hasReversed) { |
245 | switch (reversingMode) { |
246 | default: |
247 | case QQuickSmoothedAnimation::Eased: |
248 | initialVelocity = -trackVelocity; |
249 | break; |
250 | case QQuickSmoothedAnimation::Sync: |
251 | QQmlPropertyPrivate::write(that: target, to, |
252 | QQmlPropertyData::BypassInterceptor |
253 | | QQmlPropertyData::DontRemoveBinding); |
254 | trackVelocity = 0; |
255 | stop(); |
256 | return; |
257 | case QQuickSmoothedAnimation::Immediate: |
258 | initialVelocity = 0; |
259 | break; |
260 | } |
261 | } |
262 | |
263 | trackVelocity = initialVelocity; |
264 | |
265 | invert = (to < initialValue); |
266 | |
267 | if (!recalc()) { |
268 | QQmlPropertyPrivate::write(that: target, to, |
269 | QQmlPropertyData::BypassInterceptor |
270 | | QQmlPropertyData::DontRemoveBinding); |
271 | stop(); |
272 | return; |
273 | } |
274 | } |
275 | |
276 | void QSmoothedAnimation::debugAnimation(QDebug d) const |
277 | { |
278 | d << "SmoothedAnimationJob("<< Qt::hex << (const void *) this << Qt::dec << ")"<< "duration:"<< userDuration |
279 | << "velocity:"<< velocity << "target:"<< target.object() << "property:"<< target.name() |
280 | << "to:"<< to << "current velocity:"<< trackVelocity; |
281 | } |
282 | |
283 | /*! |
284 | \qmltype SmoothedAnimation |
285 | \nativetype QQuickSmoothedAnimation |
286 | \inqmlmodule QtQuick |
287 | \ingroup qtquick-transitions-animations |
288 | \inherits NumberAnimation |
289 | \brief Allows a property to smoothly track a value. |
290 | |
291 | A SmoothedAnimation animates a property's value to a set target value |
292 | using an ease in/out quad easing curve. When the target value changes, |
293 | the easing curves used to animate between the old and new target values |
294 | are smoothly spliced together to create a smooth movement to the new |
295 | target value that maintains the current velocity. |
296 | |
297 | The follow example shows one \l Rectangle tracking the position of another |
298 | using SmoothedAnimation. The green rectangle's \c x and \c y values are |
299 | bound to those of the red rectangle. Whenever these values change, the |
300 | green rectangle smoothly animates to its new position: |
301 | |
302 | \snippet qml/smoothedanimation.qml 0 |
303 | |
304 | A SmoothedAnimation can be configured by setting the \l velocity at which the |
305 | animation should occur, or the \l duration that the animation should take. |
306 | If both the \l velocity and \l duration are specified, the one that results in |
307 | the quickest animation is chosen for each change in the target value. |
308 | |
309 | For example, animating from 0 to 800 will take 4 seconds if a velocity |
310 | of 200 is set, will take 8 seconds with a duration of 8000 set, and will |
311 | take 4 seconds with both a velocity of 200 and a duration of 8000 set. |
312 | Animating from 0 to 20000 will take 10 seconds if a velocity of 200 is set, |
313 | will take 8 seconds with a duration of 8000 set, and will take 8 seconds |
314 | with both a velocity of 200 and a duration of 8000 set. |
315 | |
316 | The default velocity of SmoothedAnimation is 200 units/second. Note that if the range of the |
317 | value being animated is small, then the velocity will need to be adjusted |
318 | appropriately. For example, the opacity of an item ranges from 0 - 1.0. |
319 | To enable a smooth animation in this range the velocity will need to be |
320 | set to a value such as 0.5 units/second. Animating from 0 to 1.0 with a velocity |
321 | of 0.5 will take 2000 ms to complete. |
322 | |
323 | Like any other animation type, a SmoothedAnimation can be applied in a |
324 | number of ways, including transitions, behaviors and property value |
325 | sources. The \l {Animation and Transitions in Qt Quick} documentation shows a |
326 | variety of methods for creating animations. |
327 | |
328 | \sa SpringAnimation, NumberAnimation, {Animation and Transitions in Qt Quick}, {Qt Quick Examples - Animation} |
329 | */ |
330 | |
331 | QQuickSmoothedAnimation::QQuickSmoothedAnimation(QObject *parent) |
332 | : QQuickNumberAnimation(*(new QQuickSmoothedAnimationPrivate), parent) |
333 | { |
334 | } |
335 | |
336 | QQuickSmoothedAnimationPrivate::QQuickSmoothedAnimationPrivate() |
337 | : anim(new QSmoothedAnimation) |
338 | { |
339 | } |
340 | |
341 | QQuickSmoothedAnimationPrivate::~QQuickSmoothedAnimationPrivate() |
342 | { |
343 | typedef QHash<QQmlProperty, QSmoothedAnimation* >::iterator ActiveAnimationsHashIt; |
344 | |
345 | delete anim; |
346 | for (ActiveAnimationsHashIt it = activeAnimations.begin(), end = activeAnimations.end(); it != end; ++it) |
347 | it.value()->clearTemplate(); |
348 | } |
349 | |
350 | void QQuickSmoothedAnimationPrivate::updateRunningAnimations() |
351 | { |
352 | for (QSmoothedAnimation *ease : std::as_const(t&: activeAnimations)) { |
353 | ease->maximumEasingTime = anim->maximumEasingTime; |
354 | ease->reversingMode = anim->reversingMode; |
355 | ease->velocity = anim->velocity; |
356 | ease->userDuration = anim->userDuration; |
357 | ease->init(); |
358 | } |
359 | } |
360 | |
361 | QAbstractAnimationJob* QQuickSmoothedAnimation::transition(QQuickStateActions &actions, |
362 | QQmlProperties &modified, |
363 | TransitionDirection direction, |
364 | QObject *defaultTarget) |
365 | { |
366 | Q_UNUSED(direction); |
367 | Q_D(QQuickSmoothedAnimation); |
368 | |
369 | const QQuickStateActions dataActions = QQuickPropertyAnimation::createTransitionActions(actions, modified, defaultTarget); |
370 | |
371 | QContinuingAnimationGroupJob *wrapperGroup = new QContinuingAnimationGroupJob(); |
372 | |
373 | if (!dataActions.isEmpty()) { |
374 | QSet<QAbstractAnimationJob*> anims; |
375 | for (int i = 0; i < dataActions.size(); i++) { |
376 | QSmoothedAnimation *ease; |
377 | bool isActive; |
378 | if (!d->activeAnimations.contains(key: dataActions[i].property)) { |
379 | ease = new QSmoothedAnimation(d); |
380 | d->activeAnimations.insert(key: dataActions[i].property, value: ease); |
381 | ease->target = dataActions[i].property; |
382 | isActive = false; |
383 | } else { |
384 | ease = d->activeAnimations.value(key: dataActions[i].property); |
385 | isActive = true; |
386 | } |
387 | wrapperGroup->appendAnimation(animation: initInstance(animation: ease)); |
388 | |
389 | ease->to = dataActions[i].toValue.toReal(); |
390 | |
391 | // copying public members from main value holder animation |
392 | ease->maximumEasingTime = d->anim->maximumEasingTime; |
393 | ease->reversingMode = d->anim->reversingMode; |
394 | ease->velocity = d->anim->velocity; |
395 | ease->userDuration = d->anim->userDuration; |
396 | |
397 | ease->initialVelocity = ease->trackVelocity; |
398 | |
399 | if (isActive) |
400 | ease->prepareForRestart(); |
401 | anims.insert(value: ease); |
402 | } |
403 | |
404 | const auto copy = d->activeAnimations; |
405 | for (QSmoothedAnimation *ease : copy) { |
406 | if (!anims.contains(value: ease)) { |
407 | ease->clearTemplate(); |
408 | d->activeAnimations.remove(key: ease->target); |
409 | } |
410 | } |
411 | } |
412 | return wrapperGroup; |
413 | } |
414 | |
415 | /*! |
416 | \qmlproperty enumeration QtQuick::SmoothedAnimation::reversingMode |
417 | |
418 | Sets how the SmoothedAnimation behaves if an animation direction is reversed. |
419 | |
420 | Possible values are: |
421 | |
422 | \value SmoothedAnimation.Eased |
423 | (default) the animation will smoothly decelerate, and then reverse direction |
424 | \value SmoothedAnimation.Immediate |
425 | the animation will immediately begin accelerating in the reverse direction, beginning with a velocity of 0 |
426 | \value SmoothedAnimation.Sync |
427 | the property is immediately set to the target value |
428 | */ |
429 | QQuickSmoothedAnimation::ReversingMode QQuickSmoothedAnimation::reversingMode() const |
430 | { |
431 | Q_D(const QQuickSmoothedAnimation); |
432 | return (QQuickSmoothedAnimation::ReversingMode) d->anim->reversingMode; |
433 | } |
434 | |
435 | void QQuickSmoothedAnimation::setReversingMode(ReversingMode m) |
436 | { |
437 | Q_D(QQuickSmoothedAnimation); |
438 | if (d->anim->reversingMode == m) |
439 | return; |
440 | |
441 | d->anim->reversingMode = m; |
442 | emit reversingModeChanged(); |
443 | d->updateRunningAnimations(); |
444 | } |
445 | |
446 | /*! |
447 | \qmlproperty int QtQuick::SmoothedAnimation::duration |
448 | |
449 | This property holds the animation duration, in msecs, used when tracking the source. |
450 | |
451 | Setting this to -1 (the default) disables the duration value. |
452 | |
453 | If the velocity value and the duration value are both enabled, then the animation will |
454 | use whichever gives the shorter duration. |
455 | */ |
456 | int QQuickSmoothedAnimation::duration() const |
457 | { |
458 | Q_D(const QQuickSmoothedAnimation); |
459 | return d->anim->userDuration; |
460 | } |
461 | |
462 | void QQuickSmoothedAnimation::setDuration(int duration) |
463 | { |
464 | Q_D(QQuickSmoothedAnimation); |
465 | if (duration != -1) |
466 | QQuickNumberAnimation::setDuration(duration); |
467 | if(duration == d->anim->userDuration) |
468 | return; |
469 | d->anim->userDuration = duration; |
470 | d->updateRunningAnimations(); |
471 | } |
472 | |
473 | qreal QQuickSmoothedAnimation::velocity() const |
474 | { |
475 | Q_D(const QQuickSmoothedAnimation); |
476 | return d->anim->velocity; |
477 | } |
478 | |
479 | /*! |
480 | \qmlproperty real QtQuick::SmoothedAnimation::velocity |
481 | |
482 | This property holds the average velocity allowed when tracking the 'to' value. |
483 | |
484 | The default velocity of SmoothedAnimation is 200 units/second. |
485 | |
486 | Setting this to -1 disables the velocity value. |
487 | |
488 | If the velocity value and the duration value are both enabled, then the animation will |
489 | use whichever gives the shorter duration. |
490 | */ |
491 | void QQuickSmoothedAnimation::setVelocity(qreal v) |
492 | { |
493 | Q_D(QQuickSmoothedAnimation); |
494 | if (d->anim->velocity == v) |
495 | return; |
496 | |
497 | d->anim->velocity = v; |
498 | emit velocityChanged(); |
499 | d->updateRunningAnimations(); |
500 | } |
501 | |
502 | /*! |
503 | \qmlproperty int QtQuick::SmoothedAnimation::maximumEasingTime |
504 | |
505 | This property specifies the maximum time, in msecs, any "eases" during the follow should take. |
506 | Setting this property causes the velocity to "level out" after at a time. Setting |
507 | a negative value reverts to the normal mode of easing over the entire animation |
508 | duration. |
509 | |
510 | The default value is -1. |
511 | */ |
512 | int QQuickSmoothedAnimation::maximumEasingTime() const |
513 | { |
514 | Q_D(const QQuickSmoothedAnimation); |
515 | return d->anim->maximumEasingTime; |
516 | } |
517 | |
518 | void QQuickSmoothedAnimation::setMaximumEasingTime(int v) |
519 | { |
520 | Q_D(QQuickSmoothedAnimation); |
521 | if(v == d->anim->maximumEasingTime) |
522 | return; |
523 | d->anim->maximumEasingTime = v; |
524 | emit maximumEasingTimeChanged(); |
525 | d->updateRunningAnimations(); |
526 | } |
527 | |
528 | QT_END_NAMESPACE |
529 | |
530 | #include "moc_qquicksmoothedanimation_p_p.cpp" |
531 | |
532 | #include "moc_qquicksmoothedanimation_p.cpp" |
533 |
Definitions
- QSmoothedAnimationTimer
- ~QSmoothedAnimationTimer
- stopAnimation
- QSmoothedAnimation
- ~QSmoothedAnimation
- restart
- prepareForRestart
- updateState
- delayedStop
- duration
- recalc
- easeFollow
- updateCurrentTime
- init
- debugAnimation
- QQuickSmoothedAnimation
- QQuickSmoothedAnimationPrivate
- ~QQuickSmoothedAnimationPrivate
- updateRunningAnimations
- transition
- reversingMode
- setReversingMode
- duration
- setDuration
- velocity
- setVelocity
- maximumEasingTime
Start learning QML with our Intro Training
Find out more