1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qquicksmoothedanimation_p.h"
43 #include "qquicksmoothedanimation_p_p.h"
45 #include "qquickanimation_p_p.h"
47 #include <qqmlproperty.h>
48 #include <private/qqmlproperty_p.h>
50 #include <private/qqmlglobal_p.h>
52 #include <QtCore/qdebug.h>
56 #define DELAY_STOP_TIMER_INTERVAL 32
61 QSmoothedAnimationTimer::QSmoothedAnimationTimer(QSmoothedAnimation *animation, QObject *parent)
63 , m_animation(animation)
65 connect(this, SIGNAL(timeout()), this, SLOT(stopAnimation()));
68 QSmoothedAnimationTimer::~QSmoothedAnimationTimer()
72 void QSmoothedAnimationTimer::stopAnimation()
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)
83 delayedStopTimer->setInterval(DELAY_STOP_TIMER_INTERVAL);
84 delayedStopTimer->setSingleShot(true);
87 QSmoothedAnimation::~QSmoothedAnimation()
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);
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);
109 void QSmoothedAnimation::restart()
111 initialVelocity = trackVelocity;
118 void QSmoothedAnimation::prepareForRestart()
120 initialVelocity = trackVelocity;
122 //we are joining a new wrapper group while running, our times need to be restarted
128 //we'll be started when the group starts, which will force an init()
132 void QSmoothedAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/)
134 if (newState == QAbstractAnimationJob::Running)
138 void QSmoothedAnimation::delayedStop()
140 if (!delayedStopTimer->isActive())
141 delayedStopTimer->start();
144 int QSmoothedAnimation::duration() const
149 bool QSmoothedAnimation::recalc()
151 s = to - initialValue;
152 vi = initialVelocity;
154 s = (invert? -1.0: 1.0) * s;
156 if (userDuration > 0 && velocity > 0) {
158 if (tf > (userDuration / 1000.)) tf = (userDuration / 1000.);
159 } else if (userDuration > 0) {
160 tf = userDuration / 1000.;
161 } else if (velocity > 0) {
167 finalDuration = ceil(tf * 1000.0);
169 if (maximumEasingTime == 0) {
177 } else if (maximumEasingTime != -1 && tf > (maximumEasingTime / 1000.)) {
178 qreal met = maximumEasingTime / 1000.;
188 a = (s - (vi * tf - 0.5 * vi * ta)) / (tf * ta - ta * ta);
193 sp = vi * ta + 0.5 * a * tp * tp;
194 sd = sp + vp * (tf - 2 * ta);
197 qreal c1 = 0.25 * tf * tf;
198 qreal c2 = 0.5 * vi * tf - s;
199 qreal c3 = -0.25 * vi * vi;
201 qreal a1 = (-c2 + sqrt(c2 * c2 - 4 * c1 * c3)) / (2. * c1);
203 qreal tp1 = 0.5 * tf - 0.5 * vi / a1;
204 qreal vp1 = a1 * tp1 + vi;
206 qreal sp1 = 0.5 * a1 * tp1 * tp1 + vi * tp1;
219 qreal QSmoothedAnimation::easeFollow(qreal time_seconds)
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) {
228 value = sp + time_seconds * vp;
229 } else if (time_seconds < tf) {
231 trackVelocity = vp - time_seconds * a;
232 value = sd - 0.5 * d * time_seconds * time_seconds + vp * time_seconds;
239 // to normalize 's' between [0..1], divide 'value' by 's'
243 void QSmoothedAnimation::updateCurrentTime(int t)
245 qreal time_seconds = useDelta ? qreal(QQmlAnimationTimer::instance()->currentDelta()) / 1000. : qreal(t - lastTime) / 1000.;
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);
256 void QSmoothedAnimation::init()
263 if (delayedStopTimer->isActive())
264 delayedStopTimer->stop();
266 initialValue = target.read().toReal();
267 lastTime = this->currentTime();
269 if (to == initialValue) {
274 bool hasReversed = trackVelocity != 0. &&
275 ((!invert) == ((initialValue - to) > 0));
278 switch (reversingMode) {
280 case QQuickSmoothedAnimation::Eased:
281 initialVelocity = -trackVelocity;
283 case QQuickSmoothedAnimation::Sync:
284 QQmlPropertyPrivate::write(target, to,
285 QQmlPropertyPrivate::BypassInterceptor
286 | QQmlPropertyPrivate::DontRemoveBinding);
290 case QQuickSmoothedAnimation::Immediate:
296 trackVelocity = initialVelocity;
298 invert = (to < initialValue);
301 QQmlPropertyPrivate::write(target, to,
302 QQmlPropertyPrivate::BypassInterceptor
303 | QQmlPropertyPrivate::DontRemoveBinding);
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.
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.
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:
327 \snippet doc/src/snippets/qml/smoothedanimation.qml 0
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.
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.
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.
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.
353 \sa SpringAnimation, NumberAnimation, {QML Animation and Transitions}, {declarative/animation/basics}{Animation basics example}
356 QQuickSmoothedAnimation::QQuickSmoothedAnimation(QObject *parent)
357 : QQuickNumberAnimation(*(new QQuickSmoothedAnimationPrivate), parent)
361 QQuickSmoothedAnimation::~QQuickSmoothedAnimation()
366 QQuickSmoothedAnimationPrivate::QQuickSmoothedAnimationPrivate()
369 anim = new QSmoothedAnimation;
372 QQuickSmoothedAnimationPrivate::~QQuickSmoothedAnimationPrivate()
375 QHash<QQmlProperty, QSmoothedAnimation* >::iterator it;
376 for (it = activeAnimations.begin(); it != activeAnimations.end(); ++it) {
377 it.value()->clearTemplate();
381 void QQuickSmoothedAnimationPrivate::updateRunningAnimations()
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;
392 QAbstractAnimationJob* QQuickSmoothedAnimation::transition(QQuickStateActions &actions,
393 QQmlProperties &modified,
394 TransitionDirection direction,
395 QObject *defaultTarget)
398 Q_D(QQuickSmoothedAnimation);
400 QQuickStateActions dataActions = QQuickPropertyAnimation::createTransitionActions(actions, modified, defaultTarget);
402 QParallelAnimationGroupJob *wrapperGroup = new QParallelAnimationGroupJob();
404 if (!dataActions.isEmpty()) {
405 QSet<QAbstractAnimationJob*> anims;
406 for (int i = 0; i < dataActions.size(); i++) {
407 QSmoothedAnimation *ease;
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;
415 ease = d->activeAnimations.value(dataActions[i].property);
418 wrapperGroup->appendAnimation(initInstance(ease));
420 ease->to = dataActions[i].toValue.toReal();
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;
428 ease->initialVelocity = ease->trackVelocity;
431 ease->prepareForRestart();
435 foreach (QSmoothedAnimation *ease, d->activeAnimations.values()){
436 if (!anims.contains(ease)) {
437 ease->clearTemplate();
438 d->activeAnimations.remove(ease->target);
446 \qmlproperty enumeration QtQuick2::SmoothedAnimation::reversingMode
448 Sets how the SmoothedAnimation behaves if an animation direction is reversed.
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
458 QQuickSmoothedAnimation::ReversingMode QQuickSmoothedAnimation::reversingMode() const
460 Q_D(const QQuickSmoothedAnimation);
461 return (QQuickSmoothedAnimation::ReversingMode) d->anim->reversingMode;
464 void QQuickSmoothedAnimation::setReversingMode(ReversingMode m)
466 Q_D(QQuickSmoothedAnimation);
467 if (d->anim->reversingMode == m)
470 d->anim->reversingMode = m;
471 emit reversingModeChanged();
472 d->updateRunningAnimations();
476 \qmlproperty int QtQuick2::SmoothedAnimation::duration
478 This property holds the animation duration, in msecs, used when tracking the source.
480 Setting this to -1 (the default) disables the duration value.
482 If the velocity value and the duration value are both enabled, then the animation will
483 use whichever gives the shorter duration.
485 int QQuickSmoothedAnimation::duration() const
487 Q_D(const QQuickSmoothedAnimation);
488 return d->anim->userDuration;
491 void QQuickSmoothedAnimation::setDuration(int duration)
493 Q_D(QQuickSmoothedAnimation);
495 QQuickNumberAnimation::setDuration(duration);
496 if(duration == d->anim->userDuration)
498 d->anim->userDuration = duration;
499 d->updateRunningAnimations();
502 qreal QQuickSmoothedAnimation::velocity() const
504 Q_D(const QQuickSmoothedAnimation);
505 return d->anim->velocity;
509 \qmlproperty real QtQuick2::SmoothedAnimation::velocity
511 This property holds the average velocity allowed when tracking the 'to' value.
513 The default velocity of SmoothedAnimation is 200 units/second.
515 Setting this to -1 disables the velocity value.
517 If the velocity value and the duration value are both enabled, then the animation will
518 use whichever gives the shorter duration.
520 void QQuickSmoothedAnimation::setVelocity(qreal v)
522 Q_D(QQuickSmoothedAnimation);
523 if (d->anim->velocity == v)
526 d->anim->velocity = v;
527 emit velocityChanged();
528 d->updateRunningAnimations();
532 \qmlproperty int QtQuick2::SmoothedAnimation::maximumEasingTime
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
539 The default value is -1.
541 int QQuickSmoothedAnimation::maximumEasingTime() const
543 Q_D(const QQuickSmoothedAnimation);
544 return d->anim->maximumEasingTime;
547 void QQuickSmoothedAnimation::setMaximumEasingTime(int v)
549 Q_D(QQuickSmoothedAnimation);
550 if(v == d->anim->maximumEasingTime)
552 d->anim->maximumEasingTime = v;
553 emit maximumEasingTimeChanged();
554 d->updateRunningAnimations();