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
6
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
22
23
25 : QTimer(parent)
26 , m_animation(animation)
27{
28 connect(this, SIGNAL(timeout()), this, SLOT(stopAnimation()));
29}
30
34
35void QSmoothedAnimationTimer::stopAnimation()
36{
37 m_animation->stop();
38}
39
40QSmoothedAnimation::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
50QSmoothedAnimation::~QSmoothedAnimation()
51{
52 delete delayedStopTimer;
53 if (animationTemplate) {
54 if (target.object()) {
55 auto it = animationTemplate->activeAnimations.constFind(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
70void QSmoothedAnimation::restart()
71{
72 initialVelocity = trackVelocity;
73 if (isRunning())
74 init();
75 else
76 start();
77}
78
79void 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
93void QSmoothedAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/)
94{
95 if (newState == QAbstractAnimationJob::Running)
96 init();
97}
98
99void QSmoothedAnimation::delayedStop()
100{
101 if (!delayedStopTimer->isActive())
102 delayedStopTimer->start();
103}
104
105int QSmoothedAnimation::duration() const
106{
107 return -1;
108}
109
110bool 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(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(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
180qreal 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
204void 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(target, initialValue + value,
219 QQmlPropertyData::BypassInterceptor
220 | QQmlPropertyData::DontRemoveBinding);
221}
222
223void 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(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(target, to,
269 QQmlPropertyData::BypassInterceptor
270 | QQmlPropertyData::DontRemoveBinding);
271 stop();
272 return;
273 }
274}
275
276void 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
331QQuickSmoothedAnimation::QQuickSmoothedAnimation(QObject *parent)
332: QQuickNumberAnimation(*(new QQuickSmoothedAnimationPrivate), parent)
333{
334}
335
336QQuickSmoothedAnimationPrivate::QQuickSmoothedAnimationPrivate()
337 : anim(new QSmoothedAnimation)
338{
339}
340
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
351{
352 for (QSmoothedAnimation *ease : std::as_const(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
361QAbstractAnimationJob* 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(dataActions[i].property)) {
379 ease = new QSmoothedAnimation(d);
380 d->activeAnimations.insert(dataActions[i].property, ease);
381 ease->target = dataActions[i].property;
382 isActive = false;
383 } else {
384 ease = d->activeAnimations.value(dataActions[i].property);
385 isActive = true;
386 }
387 wrapperGroup->appendAnimation(initInstance(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(ease);
402 }
403
404 const auto copy = d->activeAnimations;
405 for (QSmoothedAnimation *ease : copy) {
406 if (!anims.contains(ease)) {
407 ease->clearTemplate();
408 d->activeAnimations.remove(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*/
429QQuickSmoothedAnimation::ReversingMode QQuickSmoothedAnimation::reversingMode() const
430{
431 Q_D(const QQuickSmoothedAnimation);
432 return (QQuickSmoothedAnimation::ReversingMode) d->anim->reversingMode;
433}
434
435void 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*/
456int QQuickSmoothedAnimation::duration() const
457{
458 Q_D(const QQuickSmoothedAnimation);
459 return d->anim->userDuration;
460}
461
462void 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
473qreal 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*/
491void 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*/
512int QQuickSmoothedAnimation::maximumEasingTime() const
513{
514 Q_D(const QQuickSmoothedAnimation);
515 return d->anim->maximumEasingTime;
516}
517
518void 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
528QT_END_NAMESPACE
529
530#include "moc_qquicksmoothedanimation_p_p.cpp"
531
532#include "moc_qquicksmoothedanimation_p.cpp"
QObject * parent
Definition qobject.h:73
Combined button and popup list for selecting options.
#define DELAY_STOP_TIMER_INTERVAL