Initial bundle support
[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     \qmlclass SmoothedAnimation QQuickSmoothedAnimation
311     \inqmlmodule QtQuick 2
312     \ingroup qml-animation-transition
313     \inherits NumberAnimation
314     \brief The SmoothedAnimation element allows a property to smoothly track a value.
315
316     A SmoothedAnimation animates a property's value to a set target value
317     using an ease in/out quad easing curve.  When the target value changes,
318     the easing curves used to animate between the old and new target values
319     are smoothly spliced together to create a smooth movement to the new
320     target value that maintains the current velocity.
321
322     The follow example shows one \l Rectangle tracking the position of another
323     using SmoothedAnimation. The green rectangle's \c x and \c y values are
324     bound to those of the red rectangle. Whenever these values change, the
325     green rectangle smoothly animates to its new position:
326
327     \snippet doc/src/snippets/qml/smoothedanimation.qml 0
328
329     A SmoothedAnimation can be configured by setting the \l velocity at which the
330     animation should occur, or the \l duration that the animation should take.
331     If both the \l velocity and \l duration are specified, the one that results in
332     the quickest animation is chosen for each change in the target value.
333
334     For example, animating from 0 to 800 will take 4 seconds if a velocity
335     of 200 is set, will take 8 seconds with a duration of 8000 set, and will
336     take 4 seconds with both a velocity of 200 and a duration of 8000 set.
337     Animating from 0 to 20000 will take 10 seconds if a velocity of 200 is set,
338     will take 8 seconds with a duration of 8000 set, and will take 8 seconds
339     with both a velocity of 200 and a duration of 8000 set.
340
341     The default velocity of SmoothedAnimation is 200 units/second.  Note that if the range of the
342     value being animated is small, then the velocity will need to be adjusted
343     appropriately.  For example, the opacity of an item ranges from 0 - 1.0.
344     To enable a smooth animation in this range the velocity will need to be
345     set to a value such as 0.5 units/second.  Animating from 0 to 1.0 with a velocity
346     of 0.5 will take 2000 ms to complete.
347
348     Like any other animation element, a SmoothedAnimation can be applied in a
349     number of ways, including transitions, behaviors and property value
350     sources. The \l {QML Animation and Transitions} documentation shows a
351     variety of methods for creating animations.
352
353     \sa SpringAnimation, NumberAnimation, {QML Animation and Transitions}, {declarative/animation/basics}{Animation basics example}
354 */
355
356 QQuickSmoothedAnimation::QQuickSmoothedAnimation(QObject *parent)
357 : QQuickNumberAnimation(*(new QQuickSmoothedAnimationPrivate), parent)
358 {
359 }
360
361 QQuickSmoothedAnimation::~QQuickSmoothedAnimation()
362 {
363
364 }
365
366 QQuickSmoothedAnimationPrivate::QQuickSmoothedAnimationPrivate()
367     : anim(0)
368 {
369     anim = new QSmoothedAnimation;
370 }
371
372 QQuickSmoothedAnimationPrivate::~QQuickSmoothedAnimationPrivate()
373 {
374     delete anim;
375     QHash<QQmlProperty, QSmoothedAnimation* >::iterator it;
376     for (it = activeAnimations.begin(); it != activeAnimations.end(); ++it) {
377         it.value()->clearTemplate();
378     }
379 }
380
381 void QQuickSmoothedAnimationPrivate::updateRunningAnimations()
382 {
383     foreach(QSmoothedAnimation* ease, activeAnimations.values()){
384         ease->maximumEasingTime = anim->maximumEasingTime;
385         ease->reversingMode = anim->reversingMode;
386         ease->velocity = anim->velocity;
387         ease->userDuration = anim->userDuration;
388         ease->init();
389     }
390 }
391
392 QAbstractAnimationJob* QQuickSmoothedAnimation::transition(QQuickStateActions &actions,
393                                                QQmlProperties &modified,
394                                                TransitionDirection direction,
395                                                QObject *defaultTarget)
396 {
397     Q_UNUSED(direction);
398     Q_D(QQuickSmoothedAnimation);
399
400     QQuickStateActions dataActions = QQuickPropertyAnimation::createTransitionActions(actions, modified, defaultTarget);
401
402     QParallelAnimationGroupJob *wrapperGroup = new QParallelAnimationGroupJob();
403
404     if (!dataActions.isEmpty()) {
405         QSet<QAbstractAnimationJob*> anims;
406         for (int i = 0; i < dataActions.size(); i++) {
407             QSmoothedAnimation *ease;
408             bool isActive;
409             if (!d->activeAnimations.contains(dataActions[i].property)) {
410                 ease = new QSmoothedAnimation(d);
411                 d->activeAnimations.insert(dataActions[i].property, ease);
412                 ease->target = dataActions[i].property;
413                 isActive = false;
414             } else {
415                 ease = d->activeAnimations.value(dataActions[i].property);
416                 isActive = true;
417             }
418             wrapperGroup->appendAnimation(initInstance(ease));
419
420             ease->to = dataActions[i].toValue.toReal();
421
422             // copying public members from main value holder animation
423             ease->maximumEasingTime = d->anim->maximumEasingTime;
424             ease->reversingMode = d->anim->reversingMode;
425             ease->velocity = d->anim->velocity;
426             ease->userDuration = d->anim->userDuration;
427
428             ease->initialVelocity = ease->trackVelocity;
429
430             if (isActive)
431                 ease->prepareForRestart();
432             anims.insert(ease);
433         }
434
435         foreach (QSmoothedAnimation *ease, d->activeAnimations.values()){
436             if (!anims.contains(ease)) {
437                 ease->clearTemplate();
438                 d->activeAnimations.remove(ease->target);
439             }
440         }
441     }
442     return wrapperGroup;
443 }
444
445 /*!
446     \qmlproperty enumeration QtQuick2::SmoothedAnimation::reversingMode
447
448     Sets how the SmoothedAnimation behaves if an animation direction is reversed.
449
450     Possible values are:
451
452     \list
453     \li SmoothedAnimation.Eased (default) - the animation will smoothly decelerate, and then reverse direction
454     \li SmoothedAnimation.Immediate - the animation will immediately begin accelerating in the reverse direction, beginning with a velocity of 0
455     \li SmoothedAnimation.Sync - the property is immediately set to the target value
456     \endlist
457 */
458 QQuickSmoothedAnimation::ReversingMode QQuickSmoothedAnimation::reversingMode() const
459 {
460     Q_D(const QQuickSmoothedAnimation);
461     return (QQuickSmoothedAnimation::ReversingMode) d->anim->reversingMode;
462 }
463
464 void QQuickSmoothedAnimation::setReversingMode(ReversingMode m)
465 {
466     Q_D(QQuickSmoothedAnimation);
467     if (d->anim->reversingMode == m)
468         return;
469
470     d->anim->reversingMode = m;
471     emit reversingModeChanged();
472     d->updateRunningAnimations();
473 }
474
475 /*!
476     \qmlproperty int QtQuick2::SmoothedAnimation::duration
477
478     This property holds the animation duration, in msecs, used when tracking the source.
479
480     Setting this to -1 (the default) disables the duration value.
481
482     If the velocity value and the duration value are both enabled, then the animation will
483     use whichever gives the shorter duration.
484 */
485 int QQuickSmoothedAnimation::duration() const
486 {
487     Q_D(const QQuickSmoothedAnimation);
488     return d->anim->userDuration;
489 }
490
491 void QQuickSmoothedAnimation::setDuration(int duration)
492 {
493     Q_D(QQuickSmoothedAnimation);
494     if (duration != -1)
495         QQuickNumberAnimation::setDuration(duration);
496     if(duration == d->anim->userDuration)
497         return;
498     d->anim->userDuration = duration;
499     d->updateRunningAnimations();
500 }
501
502 qreal QQuickSmoothedAnimation::velocity() const
503 {
504     Q_D(const QQuickSmoothedAnimation);
505     return d->anim->velocity;
506 }
507
508 /*!
509     \qmlproperty real QtQuick2::SmoothedAnimation::velocity
510
511     This property holds the average velocity allowed when tracking the 'to' value.
512
513     The default velocity of SmoothedAnimation is 200 units/second.
514
515     Setting this to -1 disables the velocity value.
516
517     If the velocity value and the duration value are both enabled, then the animation will
518     use whichever gives the shorter duration.
519 */
520 void QQuickSmoothedAnimation::setVelocity(qreal v)
521 {
522     Q_D(QQuickSmoothedAnimation);
523     if (d->anim->velocity == v)
524         return;
525
526     d->anim->velocity = v;
527     emit velocityChanged();
528     d->updateRunningAnimations();
529 }
530
531 /*!
532     \qmlproperty int QtQuick2::SmoothedAnimation::maximumEasingTime
533
534     This property specifies the maximum time, in msecs, any "eases" during the follow should take.
535     Setting this property causes the velocity to "level out" after at a time.  Setting
536     a negative value reverts to the normal mode of easing over the entire animation
537     duration.
538
539     The default value is -1.
540 */
541 int QQuickSmoothedAnimation::maximumEasingTime() const
542 {
543     Q_D(const QQuickSmoothedAnimation);
544     return d->anim->maximumEasingTime;
545 }
546
547 void QQuickSmoothedAnimation::setMaximumEasingTime(int v)
548 {
549     Q_D(QQuickSmoothedAnimation);
550     if(v == d->anim->maximumEasingTime)
551         return;
552     d->anim->maximumEasingTime = v;
553     emit maximumEasingTimeChanged();
554     d->updateRunningAnimations();
555 }
556
557 QT_END_NAMESPACE