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