/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qdeclarativeeasefollow_p.h" #include "qdeclarativeanimation_p_p.h" #include #include #include QT_BEGIN_NAMESPACE class QDeclarativeEaseFollowPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QDeclarativeEaseFollow) public: QDeclarativeEaseFollowPrivate() : source(0), velocity(200), duration(-1), maximumEasingTime(-1), reversingMode(QDeclarativeEaseFollow::Eased), initialVelocity(0), initialValue(0), invert(false), enabled(true), trackVelocity(0), clockOffset(0), lastTick(0), clock(this) {} qreal source; qreal velocity; qreal duration; qreal maximumEasingTime; QDeclarativeEaseFollow::ReversingMode reversingMode; qreal initialVelocity; qreal initialValue; bool invert; bool enabled; qreal trackVelocity; QDeclarativeMetaProperty target; int clockOffset; int lastTick; void tick(int); void clockStart(); void clockStop(); QTickAnimationProxy clock; void restart(); // Parameters for use in tick() qreal a; // Acceleration qreal d; // Deceleration qreal tf; // Total time qreal tp; // Time at which peak velocity occurs qreal td; // Time at which decelleration begins qreal vp; // Velocity at tp qreal sp; // Displacement at tp qreal sd; // Displacement at td qreal vi; // "Normalized" initialvelocity bool recalc(); }; bool QDeclarativeEaseFollowPrivate::recalc() { qreal s = source - initialValue; vi = initialVelocity; s = (invert?-1.0:1.0) * s; vi = (invert?-1.0:1.0) * vi; if (duration > 0 && velocity > 0) { tf = s / velocity; if (tf > (duration / 1000.)) tf = (duration / 1000.); } else if (duration > 0) { tf = duration / 1000.; } else if (velocity > 0) { tf = s / velocity; } else { return false; } if (maximumEasingTime == 0) { a = 0; d = 0; tp = 0; td = tf; vp = velocity; sp = 0; sd = s; } else if (maximumEasingTime != -1 && tf > (maximumEasingTime / 1000.)) { qreal met = maximumEasingTime / 1000.; td = tf - met; qreal c1 = td; qreal c2 = (tf - td) * vi - tf * velocity; qreal c3 = -0.5 * (tf - td) * vi * vi; qreal vp1 = (-c2 + sqrt(c2 * c2 - 4 * c1 * c3)) / (2. * c1); // qreal vp2 = (-c2 - sqrt(c2 * c2 - 4 * c1 * c3)) / (2. * c1); vp = vp1; a = vp / met; d = a; tp = (vp - vi) / a; sp = vi * tp + 0.5 * a * tp * tp; sd = sp + (td - tp) * vp; } else { qreal c1 = 0.25 * tf * tf; qreal c2 = 0.5 * vi * tf - s; qreal c3 = -0.25 * vi * vi; qreal a1 = (-c2 + sqrt(c2 * c2 - 4 * c1 * c3)) / (2. * c1); //qreal a2 = (-c2 - sqrt(c2 * c2 - 4 * c1 * c3)) / (2. * c1); qreal tp1 = 0.5 * tf - 0.5 * vi / a1; //qreal tp2 = 0.5 * tf - 0.5 * vi / a2; qreal vp1 = a1 * tp1 + vi; //qreal vp2 = a2 * tp2 + vi; qreal sp1 = 0.5 * a1 * tp1 * tp1 + vi * tp1; //qreal sp2 = 0.5 * a2 * tp2 * tp2 + vi * tp2; a = a1; d = a1; tp = tp1; td = tp1; vp = vp1; sp = sp1; sd = sp1; } /* qWarning() << "a:" << a << "tf:" << tf << "tp:" << tp << "vp:" << vp << "sp:" << sp << "vi:" << vi << "invert:" << invert; */ return true; } void QDeclarativeEaseFollowPrivate::clockStart() { if (clock.state() == QAbstractAnimation::Running) { clockOffset = lastTick; return; } else { clockOffset = 0; lastTick = 0; clock.start(); } } void QDeclarativeEaseFollowPrivate::clockStop() { clockOffset = 0; lastTick = 0; clock.stop(); } void QDeclarativeEaseFollowPrivate::tick(int t) { lastTick = t; t -= clockOffset; qreal time_seconds = qreal(t) / 1000.; qreal out = 0; if (time_seconds < tp) { trackVelocity = vi + time_seconds * a; trackVelocity = (invert?-1.0:1.0) * trackVelocity; qreal value = 0.5 * a * time_seconds * time_seconds + vi * time_seconds; value = (invert?-1.0:1.0) * value; target.write(initialValue + value); out = initialValue + value; } else if (time_seconds < td) { time_seconds -= tp; trackVelocity = (invert?-1.0:1.0) * vp; qreal value = sp + time_seconds * vp; value = (invert?-1.0:1.0) * value; target.write(initialValue + value); out = initialValue + value; } else if (time_seconds < tf) { time_seconds -= td; trackVelocity = vp - time_seconds * a; trackVelocity = (invert?-1.0:1.0) * trackVelocity; qreal value = sd - 0.5 * d * time_seconds * time_seconds + vp * time_seconds; value = (invert?-1.0:1.0) * value; target.write(initialValue + value); out = initialValue + value; } else { clock.stop(); trackVelocity = 0; target.write(source); } //qWarning() << out << trackVelocity << t << a; } /*! \qmlclass EaseFollow QDeclarativeEaseFollow \brief The EaseFollow element allows a property to smoothly track a value. The EaseFollow smoothly animates a property's value to a set target value using an ease in/out quad easing curve. If the target value changes while the animation is in progress, the easing curves used to animate to the old and the new target values are spliced together to avoid any obvious visual glitches. The property animation is configured by setting the velocity at which the animation should occur, or the duration that the animation should take. If both a velocity and a duration are specified, the one that results in the quickest animation is chosen for each change in the target value. For example, animating from 0 to 800 will take 4 seconds if a velocity of 200 is set, will take 8 seconds with a duration of 8000 set, and will take 4 seconds with both a velocity of 200 and a duration of 8000 set. Animating from 0 to 20000 will take 10 seconds if a velocity of 200 is set, will take 8 seconds with a duration of 8000 set, and will take 8 seconds with both a velocity of 200 and a duration of 8000 set. The follow example shows one rectangle tracking the position of another. \code import Qt 4.6 Rectangle { width: 800; height: 600; color: "blue" Rectangle { color: "green" width: 60; height: 60; x: -5; y: -5; x: EaseFollow { source: rect1.x - 5; velocity: 200 } y: EaseFollow { source: rect1.y - 5; velocity: 200 } } Rectangle { id: rect1 color: "red" width: 50; height: 50; } focus: true Keys.onRightPressed: rect1.x = rect1.x + 100 Keys.onLeftPressed: rect1.x = rect1.x - 100 Keys.onUpPressed: rect1.y = rect1.y - 100 Keys.onDownPressed: rect1.y = rect1.y + 100 } \endcode The default velocity of EaseFollow is 200 units/second. Note that if the range of the value being animated is small, then the velocity will need to be adjusted appropriately. For example, the opacity of an item ranges from 0 - 1.0. To enable a smooth animation in this range the velocity will need to be set to a value such as 0.5 units/second. Animating from 0 to 1.0 with a velocity of 0.5 will take 2000 ms to complete. \sa SpringFollow */ QDeclarativeEaseFollow::QDeclarativeEaseFollow(QObject *parent) : QObject(*(new QDeclarativeEaseFollowPrivate), parent) { } QDeclarativeEaseFollow::~QDeclarativeEaseFollow() { } /*! \qmlproperty qreal EaseFollow::source This property holds the source value which will be tracked. Bind to a property in order to track its changes. */ qreal QDeclarativeEaseFollow::sourceValue() const { Q_D(const QDeclarativeEaseFollow); return d->source; } /*! \qmlproperty enumeration EaseFollow::reversingMode Sets how the EaseFollow behaves if an animation direction is reversed. If reversing mode is \c Eased, the animation will smoothly decelerate, and then reverse direction. If the reversing mode is \c Immediate, the animation will immediately begin accelerating in the reverse direction, begining with a velocity of 0. If the reversing mode is \c Sync, the property is immediately set to the target value. */ QDeclarativeEaseFollow::ReversingMode QDeclarativeEaseFollow::reversingMode() const { Q_D(const QDeclarativeEaseFollow); return d->reversingMode; } void QDeclarativeEaseFollow::setReversingMode(ReversingMode m) { Q_D(QDeclarativeEaseFollow); if (d->reversingMode == m) return; d->reversingMode = m; emit reversingModeChanged(); } void QDeclarativeEaseFollowPrivate::restart() { if (!enabled || velocity == 0) { clockStop(); return; } initialValue = target.read().toReal(); if (source == initialValue) { clockStop(); return; } bool hasReversed = trackVelocity != 0. && ((trackVelocity > 0) == ((initialValue - source) > 0)); if (hasReversed) { switch (reversingMode) { default: case QDeclarativeEaseFollow::Eased: break; case QDeclarativeEaseFollow::Sync: target.write(source); return; case QDeclarativeEaseFollow::Immediate: initialVelocity = 0; clockStop(); break; } } trackVelocity = initialVelocity; invert = (source < initialValue); if (!recalc()) { target.write(source); clockStop(); return; } clockStart(); } void QDeclarativeEaseFollow::setSourceValue(qreal s) { Q_D(QDeclarativeEaseFollow); if (d->clock.state() == QAbstractAnimation::Running && d->source == s) return; d->source = s; d->initialVelocity = d->trackVelocity; d->restart(); emit sourceChanged(); } /*! \qmlproperty qreal EaseFollow::duration This property holds the animation duration used when tracking the source. Setting this to -1 (the default) disables the duration value. */ qreal QDeclarativeEaseFollow::duration() const { Q_D(const QDeclarativeEaseFollow); return d->duration; } void QDeclarativeEaseFollow::setDuration(qreal v) { Q_D(QDeclarativeEaseFollow); if (d->duration == v) return; d->duration = v; d->trackVelocity = 0; if (d->clock.state() == QAbstractAnimation::Running) d->restart(); emit durationChanged(); } qreal QDeclarativeEaseFollow::velocity() const { Q_D(const QDeclarativeEaseFollow); return d->velocity; } /*! \qmlproperty qreal EaseFollow::velocity This property holds the average velocity allowed when tracking the source. The default velocity of EaseFollow is 200 units/second. Setting this to -1 disables the velocity value. */ void QDeclarativeEaseFollow::setVelocity(qreal v) { Q_D(QDeclarativeEaseFollow); if (d->velocity == v) return; d->velocity = v; d->trackVelocity = 0; if (d->clock.state() == QAbstractAnimation::Running) d->restart(); emit velocityChanged(); } /*! \qmlproperty bool EaseFollow::enabled This property holds whether the target will track the source. */ bool QDeclarativeEaseFollow::enabled() const { Q_D(const QDeclarativeEaseFollow); return d->enabled; } void QDeclarativeEaseFollow::setEnabled(bool enabled) { Q_D(QDeclarativeEaseFollow); if (d->enabled == enabled) return; d->enabled = enabled; if (enabled) d->restart(); else d->clockStop(); emit enabledChanged(); } void QDeclarativeEaseFollow::setTarget(const QDeclarativeMetaProperty &t) { Q_D(QDeclarativeEaseFollow); d->target = t; } /*! \qmlproperty qreal EaseFollow::maximumEasingTime This property specifies the maximum time an "eases" during the follow should take. Setting this property causes the velocity to "level out" after at a time. Setting a negative value reverts to the normal mode of easing over the entire animation duration. The default value is -1. */ qreal QDeclarativeEaseFollow::maximumEasingTime() const { Q_D(const QDeclarativeEaseFollow); return d->maximumEasingTime; } void QDeclarativeEaseFollow::setMaximumEasingTime(qreal v) { Q_D(QDeclarativeEaseFollow); d->maximumEasingTime = v; if (d->clock.state() == QAbstractAnimation::Running) d->restart(); emit maximumEasingTimeChanged(); } QT_END_NAMESPACE