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