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