diff options
author | Michael Brasser <michael.brasser@nokia.com> | 2012-02-03 12:26:37 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-02-07 05:32:47 +0100 |
commit | ce3dee765c858a0b573d468ef8fee6b838e576d1 (patch) | |
tree | b7026a061b0b58bac6af30eaab6e610c5ebeb504 /src/declarative | |
parent | 0ca9d3f0f720e1933379ef40bc5c29253e21cba0 (diff) |
Add and use new animation backend.
The new backend improves performance, and allows us to create
multiple running animation jobs from a single Transition. It is
based off of the existing Qt animation framework.
Change-Id: Id1d0162f6e5c65bf31267f3f9f2042c354375d57
Reviewed-by: Yunqiao Yin <charles.yin@nokia.com>
Diffstat (limited to 'src/declarative')
-rw-r--r-- | src/declarative/animations/animations.pri | 15 | ||||
-rw-r--r-- | src/declarative/animations/qabstractanimationjob.cpp | 548 | ||||
-rw-r--r-- | src/declarative/animations/qabstractanimationjob_p.h | 232 | ||||
-rw-r--r-- | src/declarative/animations/qanimationgroupjob.cpp | 164 | ||||
-rw-r--r-- | src/declarative/animations/qanimationgroupjob_p.h | 93 | ||||
-rw-r--r-- | src/declarative/animations/qparallelanimationgroupjob.cpp | 226 | ||||
-rw-r--r-- | src/declarative/animations/qparallelanimationgroupjob_p.h | 81 | ||||
-rw-r--r-- | src/declarative/animations/qpauseanimationjob.cpp | 71 | ||||
-rw-r--r-- | src/declarative/animations/qpauseanimationjob_p.h | 75 | ||||
-rw-r--r-- | src/declarative/animations/qsequentialanimationgroupjob.cpp | 386 | ||||
-rw-r--r-- | src/declarative/animations/qsequentialanimationgroupjob_p.h | 108 | ||||
-rw-r--r-- | src/declarative/declarative.pro | 1 |
12 files changed, 2000 insertions, 0 deletions
diff --git a/src/declarative/animations/animations.pri b/src/declarative/animations/animations.pri new file mode 100644 index 0000000000..240ee96dce --- /dev/null +++ b/src/declarative/animations/animations.pri @@ -0,0 +1,15 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qabstractanimationjob_p.h \ + $$PWD/qanimationgroupjob_p.h \ + $$PWD/qsequentialanimationgroupjob_p.h \ + $$PWD/qparallelanimationgroupjob_p.h \ + $$PWD/qpauseanimationjob_p.h + +SOURCES += \ + $$PWD/qabstractanimationjob.cpp \ + $$PWD/qanimationgroupjob.cpp \ + $$PWD/qsequentialanimationgroupjob.cpp \ + $$PWD/qparallelanimationgroupjob.cpp \ + $$PWD/qpauseanimationjob.cpp diff --git a/src/declarative/animations/qabstractanimationjob.cpp b/src/declarative/animations/qabstractanimationjob.cpp new file mode 100644 index 0000000000..a796016bb1 --- /dev/null +++ b/src/declarative/animations/qabstractanimationjob.cpp @@ -0,0 +1,548 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qthreadstorage.h> + +#include "private/qabstractanimationjob_p.h" +#include "private/qanimationgroupjob_p.h" + +#define DEFAULT_TIMER_INTERVAL 16 + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_THREAD +Q_GLOBAL_STATIC(QThreadStorage<QDeclarativeAnimationTimer *>, animationTimer) +#endif + +QDeclarativeAnimationTimer::QDeclarativeAnimationTimer() : + QAbstractAnimationTimer(), lastTick(0), lastDelta(0), + currentAnimationIdx(0), insideTick(false), + startAnimationPending(false), stopTimerPending(false), + runningLeafAnimations(0) +{ +} + +QDeclarativeAnimationTimer *QDeclarativeAnimationTimer::instance(bool create) +{ + QDeclarativeAnimationTimer *inst; +#ifndef QT_NO_THREAD + if (create && !animationTimer()->hasLocalData()) { + inst = new QDeclarativeAnimationTimer; + animationTimer()->setLocalData(inst); + } else { + inst = animationTimer() ? animationTimer()->localData() : 0; + } +#else + static QAnimationTimer unifiedTimer; + inst = &unifiedTimer; +#endif + return inst; +} + +QDeclarativeAnimationTimer *QDeclarativeAnimationTimer::instance() +{ + return instance(true); +} + +void QDeclarativeAnimationTimer::ensureTimerUpdate() +{ + QDeclarativeAnimationTimer *inst = QDeclarativeAnimationTimer::instance(false); + QUnifiedTimer *instU = QUnifiedTimer::instance(false); + if (instU && inst && inst->isPaused) + instU->updateAnimationTimers(-1); +} + +void QDeclarativeAnimationTimer::updateAnimationsTime(qint64 delta) +{ + //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations + if (insideTick) + return; + + lastTick += delta; + lastDelta = delta; + + //we make sure we only call update time if the time has actually changed + //it might happen in some cases that the time doesn't change because events are delayed + //when the CPU load is high + if (delta) { + insideTick = true; + for (currentAnimationIdx = 0; currentAnimationIdx < animations.count(); ++currentAnimationIdx) { + QAbstractAnimationJob *animation = animations.at(currentAnimationIdx); + int elapsed = animation->m_totalCurrentTime + + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta); + animation->setCurrentTime(elapsed); + } + insideTick = false; + currentAnimationIdx = 0; + } +} + +void QDeclarativeAnimationTimer::updateAnimationTimer() +{ + QDeclarativeAnimationTimer *inst = QDeclarativeAnimationTimer::instance(false); + if (inst) + inst->restartAnimationTimer(); +} + +void QDeclarativeAnimationTimer::restartAnimationTimer() +{ + if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty()) + QUnifiedTimer::pauseAnimationTimer(this, closestPauseAnimationTimeToFinish()); + else if (isPaused) + QUnifiedTimer::resumeAnimationTimer(this); + else if (!isRegistered) + QUnifiedTimer::startAnimationTimer(this); +} + +void QDeclarativeAnimationTimer::startAnimations() +{ + startAnimationPending = false; + //force timer to update, which prevents large deltas for our newly added animations + if (!animations.isEmpty()) + QUnifiedTimer::instance()->updateAnimationTimers(-1); + + //we transfer the waiting animations into the "really running" state + animations += animationsToStart; + animationsToStart.clear(); + if (!animations.isEmpty()) + restartAnimationTimer(); +} + +void QDeclarativeAnimationTimer::stopTimer() +{ + stopTimerPending = false; + if (animations.isEmpty()) { + QUnifiedTimer::resumeAnimationTimer(this); + QUnifiedTimer::stopAnimationTimer(this); + // invalidate the start reference time + lastTick = 0; + lastDelta = 0; + } +} + +void QDeclarativeAnimationTimer::registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel) +{ + QDeclarativeAnimationTimer *inst = instance(true); //we create the instance if needed + inst->registerRunningAnimation(animation); + if (isTopLevel) { + Q_ASSERT(!animation->m_hasRegisteredTimer); + animation->m_hasRegisteredTimer = true; + inst->animationsToStart << animation; + if (!inst->startAnimationPending) { + inst->startAnimationPending = true; + QMetaObject::invokeMethod(inst, "startAnimations", Qt::QueuedConnection); + } + } +} + +void QDeclarativeAnimationTimer::unregisterAnimation(QAbstractAnimationJob *animation) +{ + QDeclarativeAnimationTimer *inst = QDeclarativeAnimationTimer::instance(false); + if (inst) { + //at this point the unified timer should have been created + //but it might also have been already destroyed in case the application is shutting down + + inst->unregisterRunningAnimation(animation); + + if (!animation->m_hasRegisteredTimer) + return; + + int idx = inst->animations.indexOf(animation); + if (idx != -1) { + inst->animations.removeAt(idx); + // this is needed if we unregister an animation while its running + if (idx <= inst->currentAnimationIdx) + --inst->currentAnimationIdx; + + if (inst->animations.isEmpty() && !inst->stopTimerPending) { + inst->stopTimerPending = true; + QMetaObject::invokeMethod(inst, "stopTimer", Qt::QueuedConnection); + } + } else { + inst->animationsToStart.removeOne(animation); + } + } + animation->m_hasRegisteredTimer = false; +} + +void QDeclarativeAnimationTimer::registerRunningAnimation(QAbstractAnimationJob *animation) +{ + if (animation->m_isGroup) + return; + + if (animation->m_isPause) { + runningPauseAnimations << animation; + } else + runningLeafAnimations++; +} + +void QDeclarativeAnimationTimer::unregisterRunningAnimation(QAbstractAnimationJob *animation) +{ + if (animation->m_isGroup) + return; + + if (animation->m_isPause) + runningPauseAnimations.removeOne(animation); + else + runningLeafAnimations--; + Q_ASSERT(runningLeafAnimations >= 0); +} + +int QDeclarativeAnimationTimer::closestPauseAnimationTimeToFinish() +{ + int closestTimeToFinish = INT_MAX; + for (int i = 0; i < runningPauseAnimations.size(); ++i) { + QAbstractAnimationJob *animation = runningPauseAnimations.at(i); + int timeToFinish; + + if (animation->direction() == QAbstractAnimationJob::Forward) + timeToFinish = animation->duration() - animation->currentLoopTime(); + else + timeToFinish = animation->currentLoopTime(); + + if (timeToFinish < closestTimeToFinish) + closestTimeToFinish = timeToFinish; + } + return closestTimeToFinish; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +QAbstractAnimationJob::QAbstractAnimationJob() + : m_isPause(false) + , m_isGroup(false) + , m_loopCount(1) + , m_group(0) + , m_direction(QAbstractAnimationJob::Forward) + , m_state(QAbstractAnimationJob::Stopped) + , m_totalCurrentTime(0) + , m_currentTime(0) + , m_currentLoop(0) + , m_hasRegisteredTimer(false) + , m_uncontrolledFinishTime(-1) + , m_wasDeleted(0) + , m_nextSibling(0) + , m_previousSibling(0) +{ +} + +QAbstractAnimationJob::~QAbstractAnimationJob() +{ + if (m_wasDeleted) + *m_wasDeleted = true; + + //we can't call stop here. Otherwise we get pure virtual calls + if (m_state != Stopped) { + State oldState = m_state; + m_state = Stopped; + stateChanged(oldState, m_state); + if (oldState == Running) + QDeclarativeAnimationTimer::unregisterAnimation(this); + } + + if (m_group) + m_group->removeAnimation(this); +} + +void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState) +{ + if (m_state == newState) + return; + + if (m_loopCount == 0) + return; + + State oldState = m_state; + int oldCurrentTime = m_currentTime; + int oldCurrentLoop = m_currentLoop; + Direction oldDirection = m_direction; + + // check if we should Rewind + if ((newState == Paused || newState == Running) && oldState == Stopped) { + //here we reset the time if needed + //we don't call setCurrentTime because this might change the way the animation + //behaves: changing the state or changing the current value + m_totalCurrentTime = m_currentTime = (m_direction == Forward) ? + 0 : (m_loopCount == -1 ? duration() : totalDuration()); + } + + m_state = newState; + //(un)registration of the animation must always happen before calls to + //virtual function (updateState) to ensure a correct state of the timer + bool isTopLevel = !m_group || m_group->isStopped(); + if (oldState == Running) { + if (newState == Paused && m_hasRegisteredTimer) + QDeclarativeAnimationTimer::ensureTimerUpdate(); + //the animation, is not running any more + QDeclarativeAnimationTimer::unregisterAnimation(this); + } else if (newState == Running) { + QDeclarativeAnimationTimer::registerAnimation(this, isTopLevel); + } + + //starting an animation qualifies as a top level loop change + if (newState == Running && oldState == Stopped && !m_group) + topLevelAnimationLoopChanged(); + + bool wasDeleted = false; + m_wasDeleted = &wasDeleted; + updateState(newState, oldState); + if (wasDeleted) + return; + m_wasDeleted = 0; + + if (newState != m_state) //this is to be safe if updateState changes the state + return; + + // Notify state change + stateChanged(newState, oldState); + if (newState != m_state) //this is to be safe if updateState changes the state + return; + + switch (m_state) { + case Paused: + break; + case Running: + { + // this ensures that the value is updated now that the animation is running + if (oldState == Stopped) { + if (isTopLevel) { + // currentTime needs to be updated if pauseTimer is active + QDeclarativeAnimationTimer::ensureTimerUpdate(); + setCurrentTime(m_totalCurrentTime); + } + } + } + break; + case Stopped: + // Leave running state. + int dura = duration(); + + if (dura == -1 || m_loopCount < 0 + || (oldDirection == Forward && (oldCurrentTime * (oldCurrentLoop + 1)) == (dura * m_loopCount)) + || (oldDirection == Backward && oldCurrentTime == 0)) { + finished(); + } + break; + } +} + +void QAbstractAnimationJob::setDirection(Direction direction) +{ + if (m_direction == direction) + return; + + if (m_state == Stopped) { + if (m_direction == Backward) { + m_currentTime = duration(); + m_currentLoop = m_loopCount - 1; + } else { + m_currentTime = 0; + m_currentLoop = 0; + } + } + + // the commands order below is important: first we need to setCurrentTime with the old direction, + // then update the direction on this and all children and finally restart the pauseTimer if needed + if (m_hasRegisteredTimer) + QDeclarativeAnimationTimer::ensureTimerUpdate(); + + m_direction = direction; + updateDirection(direction); + + if (m_hasRegisteredTimer) + // needed to update the timer interval in case of a pause animation + QDeclarativeAnimationTimer::updateAnimationTimer(); +} + +void QAbstractAnimationJob::setLoopCount(int loopCount) +{ + m_loopCount = loopCount; +} + +int QAbstractAnimationJob::totalDuration() const +{ + int dura = duration(); + if (dura <= 0) + return dura; + int loopcount = loopCount(); + if (loopcount < 0) + return -1; + return dura * loopcount; +} + +void QAbstractAnimationJob::setCurrentTime(int msecs) +{ + msecs = qMax(msecs, 0); + // Calculate new time and loop. + int dura = duration(); + int totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount); + if (totalDura != -1) + msecs = qMin(totalDura, msecs); + m_totalCurrentTime = msecs; + + // Update new values. + int oldLoop = m_currentLoop; + m_currentLoop = ((dura <= 0) ? 0 : (msecs / dura)); + if (m_currentLoop == m_loopCount) { + //we're at the end + m_currentTime = qMax(0, dura); + m_currentLoop = qMax(0, m_loopCount - 1); + } else { + if (m_direction == Forward) { + m_currentTime = (dura <= 0) ? msecs : (msecs % dura); + } else { + m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1; + if (m_currentTime == dura) + --m_currentLoop; + } + } + + if (m_currentLoop != oldLoop && !m_group) //### verify Running as well? + topLevelAnimationLoopChanged(); + + updateCurrentTime(m_currentTime); + + if (m_currentLoop != oldLoop) + currentLoopChanged(m_currentLoop); + + // All animations are responsible for stopping the animation when their + // own end state is reached; in this case the animation is time driven, + // and has reached the end. + if ((m_direction == Forward && m_totalCurrentTime == totalDura) + || (m_direction == Backward && m_totalCurrentTime == 0)) { + stop(); + } +} + +void QAbstractAnimationJob::start() +{ + if (m_state == Running) + return; + setState(Running); +} + +void QAbstractAnimationJob::stop() +{ + if (m_state == Stopped) + return; + setState(Stopped); +} + +void QAbstractAnimationJob::pause() +{ + if (m_state == Stopped) { + qWarning("QAbstractAnimationJob::pause: Cannot pause a stopped animation"); + return; + } + + setState(Paused); +} + +void QAbstractAnimationJob::resume() +{ + if (m_state != Paused) { + qWarning("QAbstractAnimationJob::resume: " + "Cannot resume an animation that is not paused"); + return; + } + setState(Running); +} + +void QAbstractAnimationJob::updateState(QAbstractAnimationJob::State newState, + QAbstractAnimationJob::State oldState) +{ + Q_UNUSED(oldState); + Q_UNUSED(newState); +} + +void QAbstractAnimationJob::updateDirection(QAbstractAnimationJob::Direction direction) +{ + Q_UNUSED(direction); +} + +void QAbstractAnimationJob::finished() +{ + //TODO: update this code so it is valid to delete the animation in animationFinished + for (int i = 0; i < changeListeners.count(); ++i) { + const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i); + if (change.types & QAbstractAnimationJob::Completion) + change.listener->animationFinished(this); + } + + if (m_group && (duration() == -1 || loopCount() < 0)) { + //this is an uncontrolled animation, need to notify the group animation we are finished + m_group->uncontrolledAnimationFinished(this); + } +} + +void QAbstractAnimationJob::stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) +{ + for (int i = 0; i < changeListeners.count(); ++i) { + const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i); + if (change.types & QAbstractAnimationJob::StateChange) + change.listener->animationStateChanged(this, newState, oldState); + } +} + +void QAbstractAnimationJob::currentLoopChanged(int currentLoop) +{ + Q_UNUSED(currentLoop); + for (int i = 0; i < changeListeners.count(); ++i) { + const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i); + if (change.types & QAbstractAnimationJob::CurrentLoop) + change.listener->animationCurrentLoopChanged(this); + } +} + +void QAbstractAnimationJob::addAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes) +{ + changeListeners.append(ChangeListener(listener, changes)); +} + +void QAbstractAnimationJob::removeAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes) +{ + changeListeners.removeOne(ChangeListener(listener, changes)); +} + + +QT_END_NAMESPACE + +//#include "moc_qabstractanimation2_p.cpp" diff --git a/src/declarative/animations/qabstractanimationjob_p.h b/src/declarative/animations/qabstractanimationjob_p.h new file mode 100644 index 0000000000..dcbc749a05 --- /dev/null +++ b/src/declarative/animations/qabstractanimationjob_p.h @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTANIMATIONJOB_P_H +#define QABSTRACTANIMATIONJOB_P_H + +#include <QtCore/QObject> +#include <QtCore/private/qabstractanimation_p.h> +#include "private/qpodvector_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QAnimationGroupJob; +class QAnimation2ChangeListener; +class Q_DECLARATIVE_EXPORT QAbstractAnimationJob +{ + Q_DISABLE_COPY(QAbstractAnimationJob) +public: + enum Direction { + Forward, + Backward + }; + + enum State { + Stopped, + Paused, + Running + }; + + QAbstractAnimationJob(); + virtual ~QAbstractAnimationJob(); + + //definition + inline QAnimationGroupJob *group() const {return m_group;} + + inline int loopCount() const {return m_loopCount;} + void setLoopCount(int loopCount); + + int totalDuration() const; + virtual int duration() const {return 0;} + + inline QAbstractAnimationJob::Direction direction() const {return m_direction;} + void setDirection(QAbstractAnimationJob::Direction direction); + + //state + inline int currentTime() const {return m_totalCurrentTime;} + inline int currentLoopTime() const {return m_currentTime;} + inline int currentLoop() const {return m_currentLoop;} + inline QAbstractAnimationJob::State state() const {return m_state;} + inline bool isRunning() { return m_state == Running; } + inline bool isStopped() { return m_state == Stopped; } + inline bool isPaused() { return m_state == Paused; } + + void setCurrentTime(int msecs); + + void start(); + void pause(); + void resume(); + void stop(); + + enum ChangeType { + Completion = 0x01, + StateChange = 0x02, + CurrentLoop = 0x04 + }; + Q_DECLARE_FLAGS(ChangeTypes, ChangeType) + + void addAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes); + void removeAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes); + + QAbstractAnimationJob *nextSibling() const { return m_nextSibling; } + QAbstractAnimationJob *previousSibling() const { return m_previousSibling; } + +protected: + virtual void updateCurrentTime(int) {} + virtual void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState); + virtual void updateDirection(QAbstractAnimationJob::Direction direction); + virtual void topLevelAnimationLoopChanged() {} + + void setState(QAbstractAnimationJob::State state); + + void finished(); + void stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState); + void currentLoopChanged(int currentLoop); + void directionChanged(QAbstractAnimationJob::Direction); + + //definition + bool m_isPause; + bool m_isGroup; + int m_loopCount; + QAnimationGroupJob *m_group; + QAbstractAnimationJob::Direction m_direction; + + //state + QAbstractAnimationJob::State m_state; + int m_totalCurrentTime; + int m_currentTime; + int m_currentLoop; + bool m_hasRegisteredTimer; + //records the finish time for an uncontrolled animation (used by animation groups) + int m_uncontrolledFinishTime; + bool *m_wasDeleted; + + struct ChangeListener { + ChangeListener(QAnimation2ChangeListener *l, QAbstractAnimationJob::ChangeTypes t) : listener(l), types(t) {} + QAnimation2ChangeListener *listener; + QAbstractAnimationJob::ChangeTypes types; + bool operator==(const ChangeListener &other) const { return listener == other.listener && types == other.types; } + }; + QPODVector<ChangeListener,4> changeListeners; + + QAbstractAnimationJob *m_nextSibling; + QAbstractAnimationJob *m_previousSibling; + + friend class QDeclarativeAnimationTimer; + friend class QAnimationGroupJob; +}; + +class Q_AUTOTEST_EXPORT QAnimation2ChangeListener +{ +public: + virtual void animationFinished(QAbstractAnimationJob *) {} + virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State, QAbstractAnimationJob::State) {} + virtual void animationCurrentLoopChanged(QAbstractAnimationJob *) {} +}; + +class Q_DECLARATIVE_EXPORT QDeclarativeAnimationTimer : public QAbstractAnimationTimer +{ + Q_OBJECT +private: + QDeclarativeAnimationTimer(); + +public: + static QDeclarativeAnimationTimer *instance(); + static QDeclarativeAnimationTimer *instance(bool create); + + static void registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel); + static void unregisterAnimation(QAbstractAnimationJob *animation); + + /* + this is used for updating the currentTime of all animations in case the pause + timer is active or, otherwise, only of the animation passed as parameter. + */ + static void ensureTimerUpdate(); + + /* + this will evaluate the need of restarting the pause timer in case there is still + some pause animations running. + */ + static void updateAnimationTimer(); + + void restartAnimationTimer(); + void updateAnimationsTime(qint64 timeStep); + + int currentDelta() { return lastDelta; } + + //useful for profiling/debugging + int runningAnimationCount() { return animations.count(); } + +private Q_SLOTS: + void startAnimations(); + void stopTimer(); + +private: + qint64 lastTick; + int lastDelta; + int currentAnimationIdx; + bool insideTick; + bool startAnimationPending; + bool stopTimerPending; + + QList<QAbstractAnimationJob*> animations, animationsToStart; + + // this is the count of running animations that are not a group neither a pause animation + int runningLeafAnimations; + QList<QAbstractAnimationJob*> runningPauseAnimations; + + void registerRunningAnimation(QAbstractAnimationJob *animation); + void unregisterRunningAnimation(QAbstractAnimationJob *animation); + + int closestPauseAnimationTimeToFinish(); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractAnimationJob::ChangeTypes) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTANIMATIONJOB_P_H diff --git a/src/declarative/animations/qanimationgroupjob.cpp b/src/declarative/animations/qanimationgroupjob.cpp new file mode 100644 index 0000000000..7e26f9778d --- /dev/null +++ b/src/declarative/animations/qanimationgroupjob.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qanimationgroupjob_p.h" + +QT_BEGIN_NAMESPACE + +QAnimationGroupJob::QAnimationGroupJob() + : QAbstractAnimationJob(), m_firstChild(0), m_lastChild(0) +{ + m_isGroup = true; +} + +QAnimationGroupJob::~QAnimationGroupJob() +{ + while (firstChild() != 0) + delete firstChild(); +} + +void QAnimationGroupJob::topLevelAnimationLoopChanged() +{ + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) + animation->topLevelAnimationLoopChanged(); +} + +void QAnimationGroupJob::appendAnimation(QAbstractAnimationJob *animation) +{ + if (QAnimationGroupJob *oldGroup = animation->m_group) + oldGroup->removeAnimation(animation); + + Q_ASSERT(!animation->previousSibling() && !animation->nextSibling()); + + if (m_lastChild) + m_lastChild->m_nextSibling = animation; + else + m_firstChild = animation; + animation->m_previousSibling = m_lastChild; + m_lastChild = animation; + + animation->m_group = this; + animationInserted(animation); +} + +void QAnimationGroupJob::prependAnimation(QAbstractAnimationJob *animation) +{ + if (QAnimationGroupJob *oldGroup = animation->m_group) + oldGroup->removeAnimation(animation); + + Q_ASSERT(!previousSibling() && !nextSibling()); + + if (m_firstChild) + m_firstChild->m_previousSibling = animation; + else + m_lastChild = animation; + animation->m_nextSibling = m_firstChild; + m_firstChild = animation; + + animation->m_group = this; + animationInserted(animation); +} + +void QAnimationGroupJob::removeAnimation(QAbstractAnimationJob *animation) +{ + Q_ASSERT(animation); + Q_ASSERT(animation->m_group == this); + QAbstractAnimationJob *prev = animation->previousSibling(); + QAbstractAnimationJob *next = animation->nextSibling(); + + if (prev) + prev->m_nextSibling = next; + else + m_firstChild = next; + + if (next) + next->m_previousSibling = prev; + else + m_lastChild = prev; + + animation->m_previousSibling = 0; + animation->m_nextSibling = 0; + + animation->m_group = 0; + animationRemoved(animation, prev, next); +} + +void QAnimationGroupJob::clear() +{ + //### should this remove and delete, or just remove? + while (firstChild() != 0) + delete firstChild(); //removeAnimation(firstChild()); +} + +void QAnimationGroupJob::resetUncontrolledAnimationsFinishTime() +{ + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + if (animation->duration() == -1 || animation->loopCount() < 0) { + resetUncontrolledAnimationFinishTime(animation); + } + } +} + +void QAnimationGroupJob::resetUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim) +{ + setUncontrolledAnimationFinishTime(anim, -1); +} + +void QAnimationGroupJob::setUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim, int time) +{ + anim->m_uncontrolledFinishTime = time; +} + +void QAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation) +{ + Q_UNUSED(animation); +} + +void QAnimationGroupJob::animationRemoved(QAbstractAnimationJob* anim, QAbstractAnimationJob* , QAbstractAnimationJob* ) +{ + resetUncontrolledAnimationFinishTime(anim); + if (!firstChild()) { + m_currentTime = 0; + stop(); + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/animations/qanimationgroupjob_p.h b/src/declarative/animations/qanimationgroupjob_p.h new file mode 100644 index 0000000000..d1917a5249 --- /dev/null +++ b/src/declarative/animations/qanimationgroupjob_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QANIMATIONGROUPJOB_P_H +#define QANIMATIONGROUPJOB_P_H + +#include "private/qabstractanimationjob_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QAnimationGroupJob : public QAbstractAnimationJob +{ + Q_DISABLE_COPY(QAnimationGroupJob) +public: + QAnimationGroupJob(); + ~QAnimationGroupJob(); + + void appendAnimation(QAbstractAnimationJob *animation); + void prependAnimation(QAbstractAnimationJob *animation); + void removeAnimation(QAbstractAnimationJob *animation); + + QAbstractAnimationJob *firstChild() const { return m_firstChild; } + QAbstractAnimationJob *lastChild() const { return m_lastChild; } + + void clear(); + + //called by QAbstractAnimationJob + virtual void uncontrolledAnimationFinished(QAbstractAnimationJob *animation); +protected: + void topLevelAnimationLoopChanged(); + + virtual void animationInserted(QAbstractAnimationJob*) { } + virtual void animationRemoved(QAbstractAnimationJob*, QAbstractAnimationJob*, QAbstractAnimationJob*); + + //TODO: confirm location of these (should any be moved into QAbstractAnimationJob?) + void resetUncontrolledAnimationsFinishTime(); + void resetUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim); + int uncontrolledAnimationFinishTime(QAbstractAnimationJob *anim) const { return anim->m_uncontrolledFinishTime; } + void setUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim, int time); + +private: + //definition + QAbstractAnimationJob *m_firstChild; + QAbstractAnimationJob *m_lastChild; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QANIMATIONGROUPJOB_P_H diff --git a/src/declarative/animations/qparallelanimationgroupjob.cpp b/src/declarative/animations/qparallelanimationgroupjob.cpp new file mode 100644 index 0000000000..ff38be382d --- /dev/null +++ b/src/declarative/animations/qparallelanimationgroupjob.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qparallelanimationgroupjob_p.h" + +QT_BEGIN_NAMESPACE + +QParallelAnimationGroupJob::QParallelAnimationGroupJob() + : QAnimationGroupJob() + , m_previousLoop(0) + , m_previousCurrentTime(0) +{ +} + +QParallelAnimationGroupJob::~QParallelAnimationGroupJob() +{ +} + +int QParallelAnimationGroupJob::duration() const +{ + int ret = 0; + + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + int currentDuration = animation->totalDuration(); + //this takes care of the case where a parallel animation group has controlled and uncontrolled + //animations, and the uncontrolled stop before the controlled + if (currentDuration == -1) + currentDuration = uncontrolledAnimationFinishTime(animation); + if (currentDuration == -1) + return -1; // Undetermined length + + ret = qMax(ret, currentDuration); + } + + return ret; +} + +void QParallelAnimationGroupJob::updateCurrentTime(int /*currentTime*/) +{ + if (!firstChild()) + return; + + if (m_currentLoop > m_previousLoop) { + // simulate completion of the loop + int dura = duration(); + if (dura > 0) { + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + if (!animation->isStopped()) + animation->setCurrentTime(dura); // will stop + } + } + } else if (m_currentLoop < m_previousLoop) { + // simulate completion of the loop seeking backwards + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + //we need to make sure the animation is in the right state + //and then rewind it + applyGroupState(animation); + animation->setCurrentTime(0); + animation->stop(); + } + } + + // finally move into the actual time of the current loop + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + const int dura = animation->totalDuration(); + //if the loopcount is bigger we should always start all animations + if (m_currentLoop > m_previousLoop + //if we're at the end of the animation, we need to start it if it wasn't already started in this loop + //this happens in Backward direction where not all animations are started at the same time + || shouldAnimationStart(animation, m_previousCurrentTime > dura /*startIfAtEnd*/)) { + applyGroupState(animation); + } + + if (animation->state() == state()) { + animation->setCurrentTime(m_currentTime); + if (dura > 0 && m_currentTime > dura) + animation->stop(); + } + } + m_previousLoop = m_currentLoop; + m_previousCurrentTime = m_currentTime; +} + +void QParallelAnimationGroupJob::updateState(QAbstractAnimationJob::State newState, + QAbstractAnimationJob::State oldState) +{ + QAnimationGroupJob::updateState(newState, oldState); + + switch (newState) { + case Stopped: + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) + animation->stop(); + break; + case Paused: + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) + if (animation->isRunning()) + animation->pause(); + break; + case Running: + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + if (oldState == Stopped) + animation->stop(); + resetUncontrolledAnimationFinishTime(animation); + animation->setDirection(m_direction); + if (shouldAnimationStart(animation, oldState == Stopped)) + animation->start(); + } + break; + } +} + +bool QParallelAnimationGroupJob::shouldAnimationStart(QAbstractAnimationJob *animation, bool startIfAtEnd) const +{ + const int dura = animation->totalDuration(); + + if (dura == -1) + return uncontrolledAnimationFinishTime(animation) == -1; + + if (startIfAtEnd) + return m_currentTime <= dura; + if (m_direction == Forward) + return m_currentTime < dura; + else //direction == Backward + return m_currentTime && m_currentTime <= dura; +} + +void QParallelAnimationGroupJob::applyGroupState(QAbstractAnimationJob *animation) +{ + switch (m_state) + { + case Running: + animation->start(); + break; + case Paused: + animation->pause(); + break; + case Stopped: + default: + break; + } +} + +void QParallelAnimationGroupJob::updateDirection(QAbstractAnimationJob::Direction direction) +{ + //we need to update the direction of the current animation + if (!isStopped()) { + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) { + animation->setDirection(direction); + } + } else { + if (direction == Forward) { + m_previousLoop = 0; + m_previousCurrentTime = 0; + } else { + // Looping backwards with loopCount == -1 does not really work well... + m_previousLoop = (m_loopCount == -1 ? 0 : m_loopCount - 1); + m_previousCurrentTime = duration(); + } + } +} + +void QParallelAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation) +{ + Q_ASSERT(animation && animation->duration() == -1 || animation->loopCount() < 0); + int uncontrolledRunningCount = 0; + + for (QAbstractAnimationJob *child = firstChild(); child; child = child->nextSibling()) { + if (child == animation) { + setUncontrolledAnimationFinishTime(animation, animation->currentTime()); + } else if (child->duration() == -1 || child->loopCount() < 0) { + if (uncontrolledAnimationFinishTime(child) == -1) + ++uncontrolledRunningCount; + } + } + + if (uncontrolledRunningCount > 0) + return; + + int maxDuration = 0; + for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) + maxDuration = qMax(maxDuration, animation->totalDuration()); + + if (m_currentTime >= maxDuration) + stop(); +} + +QT_END_NAMESPACE + diff --git a/src/declarative/animations/qparallelanimationgroupjob_p.h b/src/declarative/animations/qparallelanimationgroupjob_p.h new file mode 100644 index 0000000000..42a96b50d5 --- /dev/null +++ b/src/declarative/animations/qparallelanimationgroupjob_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPARALLELANIMATIONGROUPJOB_P_H +#define QPARALLELANIMATIONGROUPJOB_P_H + +#include "private/qanimationgroupjob_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QParallelAnimationGroupJob : public QAnimationGroupJob +{ + Q_DISABLE_COPY(QParallelAnimationGroupJob) +public: + QParallelAnimationGroupJob(); + ~QParallelAnimationGroupJob(); + + int duration() const; + +protected: + void updateCurrentTime(int currentTime); + void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState); + void updateDirection(QAbstractAnimationJob::Direction direction); + void uncontrolledAnimationFinished(QAbstractAnimationJob *animation); + +private: + bool shouldAnimationStart(QAbstractAnimationJob *animation, bool startIfAtEnd) const; + void applyGroupState(QAbstractAnimationJob *animation); + + //state + int m_previousLoop; + int m_previousCurrentTime; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPARALLELANIMATIONGROUPJOB_P_H diff --git a/src/declarative/animations/qpauseanimationjob.cpp b/src/declarative/animations/qpauseanimationjob.cpp new file mode 100644 index 0000000000..c362f5ab58 --- /dev/null +++ b/src/declarative/animations/qpauseanimationjob.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qpauseanimationjob_p.h" + +QT_BEGIN_NAMESPACE + +QPauseAnimationJob::QPauseAnimationJob(int duration) + : QAbstractAnimationJob() + , m_duration(duration) +{ + m_isPause = true; +} + +QPauseAnimationJob::~QPauseAnimationJob() +{ +} + +int QPauseAnimationJob::duration() const +{ + return m_duration; +} + +void QPauseAnimationJob::setDuration(int msecs) +{ + m_duration = msecs; +} + +void QPauseAnimationJob::updateCurrentTime(int) +{ +} + +QT_END_NAMESPACE diff --git a/src/declarative/animations/qpauseanimationjob_p.h b/src/declarative/animations/qpauseanimationjob_p.h new file mode 100644 index 0000000000..d4af832577 --- /dev/null +++ b/src/declarative/animations/qpauseanimationjob_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAUSEANIMATIONJOB_P_H +#define QPAUSEANIMATIONJOB_P_H + +#include <private/qanimationgroupjob_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QPauseAnimationJob : public QAbstractAnimationJob +{ + Q_DISABLE_COPY(QPauseAnimationJob) +public: + explicit QPauseAnimationJob(int duration = 250); + ~QPauseAnimationJob(); + + int duration() const; + void setDuration(int msecs); + +protected: + void updateCurrentTime(int); + +private: + //definition + int m_duration; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPAUSEANIMATIONJOB_P_H diff --git a/src/declarative/animations/qsequentialanimationgroupjob.cpp b/src/declarative/animations/qsequentialanimationgroupjob.cpp new file mode 100644 index 0000000000..f186f390af --- /dev/null +++ b/src/declarative/animations/qsequentialanimationgroupjob.cpp @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qsequentialanimationgroupjob_p.h" +#include "private/qpauseanimationjob_p.h" + +QT_BEGIN_NAMESPACE + +QSequentialAnimationGroupJob::QSequentialAnimationGroupJob() + : QAnimationGroupJob() + , m_currentAnimation(0) + , m_previousLoop(0) +{ +} + +QSequentialAnimationGroupJob::~QSequentialAnimationGroupJob() +{ +} + +bool QSequentialAnimationGroupJob::atEnd() const +{ + // we try to detect if we're at the end of the group + //this is true if the following conditions are true: + // 1. we're in the last loop + // 2. the direction is forward + // 3. the current animation is the last one + // 4. the current animation has reached its end + const int animTotalCurrentTime = m_currentAnimation->currentTime(); + return (m_currentLoop == m_loopCount - 1 + && m_direction == Forward + && !m_currentAnimation->nextSibling() + && animTotalCurrentTime == animationActualTotalDuration(m_currentAnimation)); +} + +int QSequentialAnimationGroupJob::animationActualTotalDuration(QAbstractAnimationJob *anim) const +{ + int ret = anim->totalDuration(); + if (ret == -1) + ret = uncontrolledAnimationFinishTime(anim); //we can try the actual duration there + return ret; +} + +QSequentialAnimationGroupJob::AnimationIndex QSequentialAnimationGroupJob::indexForCurrentTime() const +{ + Q_ASSERT(firstChild()); + + AnimationIndex ret; + QAbstractAnimationJob *anim = 0; + int duration = 0; + + for (anim = firstChild(); anim; anim = anim->nextSibling()) { + duration = animationActualTotalDuration(anim); + + // 'animation' is the current animation if one of these reasons is true: + // 1. it's duration is undefined + // 2. it ends after msecs + // 3. it is the last animation (this can happen in case there is at least 1 uncontrolled animation) + // 4. it ends exactly in msecs and the direction is backwards + if (duration == -1 || m_currentTime < (ret.timeOffset + duration) + || (m_currentTime == (ret.timeOffset + duration) && m_direction == QAbstractAnimationJob::Backward)) { + ret.animation = anim; + return ret; + } + + if (anim == m_currentAnimation) + ret.afterCurrent = true; + + // 'animation' has a non-null defined duration and is not the one at time 'msecs'. + ret.timeOffset += duration; + } + + // this can only happen when one of those conditions is true: + // 1. the duration of the group is undefined and we passed its actual duration + // 2. there are only 0-duration animations in the group + ret.timeOffset -= duration; + ret.animation = lastChild(); + return ret; +} + +void QSequentialAnimationGroupJob::restart() +{ + // restarting the group by making the first/last animation the current one + if (m_direction == Forward) { + m_previousLoop = 0; + if (m_currentAnimation == firstChild()) + activateCurrentAnimation(); + else + setCurrentAnimation(firstChild()); + } + else { // direction == Backward + m_previousLoop = m_loopCount - 1; + if (m_currentAnimation == lastChild()) + activateCurrentAnimation(); + else + setCurrentAnimation(lastChild()); + } +} + +void QSequentialAnimationGroupJob::advanceForwards(const AnimationIndex &newAnimationIndex) +{ + if (m_previousLoop < m_currentLoop) { + // we need to fast forward to the end + for (QAbstractAnimationJob *anim = m_currentAnimation; anim; anim = anim->nextSibling()) { + setCurrentAnimation(anim, true); + anim->setCurrentTime(animationActualTotalDuration(anim)); + } + // this will make sure the current animation is reset to the beginning + if (firstChild() && !firstChild()->nextSibling()) //count == 1 + // we need to force activation because setCurrentAnimation will have no effect + activateCurrentAnimation(); + else + setCurrentAnimation(firstChild(), true); + } + + // and now we need to fast forward from the current position to + for (QAbstractAnimationJob *anim = m_currentAnimation; anim && anim != newAnimationIndex.animation; anim = anim->nextSibling()) { //### WRONG, + setCurrentAnimation(anim, true); + anim->setCurrentTime(animationActualTotalDuration(anim)); + } + // setting the new current animation will happen later +} + +void QSequentialAnimationGroupJob::rewindForwards(const AnimationIndex &newAnimationIndex) +{ + if (m_previousLoop > m_currentLoop) { + // we need to fast rewind to the beginning + for (QAbstractAnimationJob *anim = m_currentAnimation; anim; anim = anim->previousSibling()) { + setCurrentAnimation(anim, true); + anim->setCurrentTime(0); + } + // this will make sure the current animation is reset to the end + if (lastChild() && !lastChild()->previousSibling()) //count == 1 + // we need to force activation because setCurrentAnimation will have no effect + activateCurrentAnimation(); + else { + setCurrentAnimation(lastChild(), true); + } + } + + // and now we need to fast rewind from the current position to + for (QAbstractAnimationJob *anim = m_currentAnimation; anim && anim != newAnimationIndex.animation; anim = anim->previousSibling()) { + setCurrentAnimation(anim, true); + anim->setCurrentTime(0); + } + // setting the new current animation will happen later +} + +int QSequentialAnimationGroupJob::duration() const +{ + int ret = 0; + + for (QAbstractAnimationJob *anim = firstChild(); anim; anim = anim->nextSibling()) { + const int currentDuration = anim->totalDuration(); + if (currentDuration == -1) + return -1; // Undetermined length + + ret += currentDuration; + } + + return ret; +} + +void QSequentialAnimationGroupJob::updateCurrentTime(int currentTime) +{ + if (!m_currentAnimation) + return; + + const QSequentialAnimationGroupJob::AnimationIndex newAnimationIndex = indexForCurrentTime(); + + // newAnimationIndex.index is the new current animation + if (m_previousLoop < m_currentLoop + || (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && newAnimationIndex.afterCurrent)) { + // advancing with forward direction is the same as rewinding with backwards direction + advanceForwards(newAnimationIndex); + } else if (m_previousLoop > m_currentLoop + || (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && !newAnimationIndex.afterCurrent)) { + // rewinding with forward direction is the same as advancing with backwards direction + rewindForwards(newAnimationIndex); + } + + setCurrentAnimation(newAnimationIndex.animation); + + const int newCurrentTime = currentTime - newAnimationIndex.timeOffset; + + if (m_currentAnimation) { + m_currentAnimation->setCurrentTime(newCurrentTime); + if (atEnd()) { + //we make sure that we don't exceed the duration here + m_currentTime += m_currentAnimation->currentTime() - newCurrentTime; + stop(); + } + } else { + //the only case where currentAnimation could be null + //is when all animations have been removed + Q_ASSERT(!firstChild()); + m_currentTime = 0; + stop(); + } + + m_previousLoop = m_currentLoop; +} + +void QSequentialAnimationGroupJob::updateState(QAbstractAnimationJob::State newState, + QAbstractAnimationJob::State oldState) +{ + QAnimationGroupJob::updateState(newState, oldState); + + if (!m_currentAnimation) + return; + + switch (newState) { + case Stopped: + m_currentAnimation->stop(); + break; + case Paused: + if (oldState == m_currentAnimation->state() && oldState == Running) + m_currentAnimation->pause(); + else + restart(); + break; + case Running: + if (oldState == m_currentAnimation->state() && oldState == Paused) + m_currentAnimation->start(); + else + restart(); + break; + } +} + +void QSequentialAnimationGroupJob::updateDirection(QAbstractAnimationJob::Direction direction) +{ + // we need to update the direction of the current animation + if (!isStopped() && m_currentAnimation) + m_currentAnimation->setDirection(direction); +} + +void QSequentialAnimationGroupJob::setCurrentAnimation(QAbstractAnimationJob *anim, bool intermediate) +{ + if (!anim) { + Q_ASSERT(!firstChild()); + m_currentAnimation = 0; + return; + } + + if (anim == m_currentAnimation) + return; + + // stop the old current animation + if (m_currentAnimation) + m_currentAnimation->stop(); + + m_currentAnimation = anim; + + activateCurrentAnimation(intermediate); +} + +void QSequentialAnimationGroupJob::activateCurrentAnimation(bool intermediate) +{ + if (!m_currentAnimation || isStopped()) + return; + + m_currentAnimation->stop(); + + // we ensure the direction is consistent with the group's direction + m_currentAnimation->setDirection(m_direction); + + // reset the finish time of the animation if it is uncontrolled + if (m_currentAnimation->totalDuration() == -1) + resetUncontrolledAnimationFinishTime(m_currentAnimation); + + m_currentAnimation->start(); + if (!intermediate && isPaused()) + m_currentAnimation->pause(); +} + +void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation) +{ + Q_ASSERT(animation == m_currentAnimation); + + setUncontrolledAnimationFinishTime(m_currentAnimation, m_currentAnimation->currentTime()); + + if ((m_direction == Forward && m_currentAnimation == lastChild()) + || (m_direction == Backward && m_currentAnimation == firstChild())) { + // we don't handle looping of a group with undefined duration + stop(); + } else if (m_direction == Forward) { + // set the current animation to be the next one + setCurrentAnimation(m_currentAnimation->nextSibling()); + } else { + // set the current animation to be the previous one + setCurrentAnimation(m_currentAnimation->previousSibling()); + } +} + +void QSequentialAnimationGroupJob::animationInserted(QAbstractAnimationJob *anim) +{ + if (m_currentAnimation == 0) + setCurrentAnimation(firstChild()); // initialize the current animation + + if (m_currentAnimation == anim->nextSibling() + && m_currentAnimation->currentTime() == 0 && m_currentAnimation->currentLoop() == 0) { + //in this case we simply insert the animation before the current one has actually started + setCurrentAnimation(anim); + } + +// TODO +// if (index < m_currentAnimationIndex || m_currentLoop != 0) { +// qWarning("QSequentialGroup::insertAnimation only supports to add animations after the current one."); +// return; //we're not affected because it is added after the current one +// } +} + +void QSequentialAnimationGroupJob::animationRemoved(QAbstractAnimationJob *anim, QAbstractAnimationJob *prev, QAbstractAnimationJob *next) +{ + QAnimationGroupJob::animationRemoved(anim, prev, next); + + Q_ASSERT(m_currentAnimation); // currentAnimation should always be set + + bool removingCurrent = anim == m_currentAnimation; + if (removingCurrent) { + if (next) + setCurrentAnimation(next); //let's try to take the next one + else if (prev) + setCurrentAnimation(prev); + else// case all animations were removed + setCurrentAnimation(0); + } + + // duration of the previous animations up to the current animation + m_currentTime = 0; + for (QAbstractAnimationJob *anim = firstChild(); anim; anim = anim->nextSibling()) { + if (anim == m_currentAnimation) + break; + m_currentTime += animationActualTotalDuration(anim); + + } + + if (!removingCurrent) { + //the current animation is not the one being removed + //so we add its current time to the current time of this group + m_currentTime += m_currentAnimation->currentTime(); + } + + //let's also update the total current time + m_totalCurrentTime = m_currentTime + m_loopCount * duration(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/animations/qsequentialanimationgroupjob_p.h b/src/declarative/animations/qsequentialanimationgroupjob_p.h new file mode 100644 index 0000000000..4c1fb2d55a --- /dev/null +++ b/src/declarative/animations/qsequentialanimationgroupjob_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSEQUENTIALANIMATIONGROUPJOB_P_H +#define QSEQUENTIALANIMATIONGROUPJOB_P_H + +#include <private/qanimationgroupjob_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QPauseAnimationJob; +class Q_DECLARATIVE_EXPORT QSequentialAnimationGroupJob : public QAnimationGroupJob +{ + Q_DISABLE_COPY(QSequentialAnimationGroupJob) +public: + QSequentialAnimationGroupJob(); + ~QSequentialAnimationGroupJob(); + + int duration() const; + + QAbstractAnimationJob *currentAnimation() const { return m_currentAnimation; } + +protected: + void updateCurrentTime(int); + void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState); + void updateDirection(QAbstractAnimationJob::Direction direction); + void uncontrolledAnimationFinished(QAbstractAnimationJob *animation); + +private: + struct AnimationIndex + { + AnimationIndex() : afterCurrent(false), timeOffset(0), animation(0) {} + // AnimationIndex points to the animation at timeOffset, skipping 0 duration animations. + // Note that the index semantic is slightly different depending on the direction. + bool afterCurrent; //whether animation is before or after m_currentAnimation //TODO: make enum Before/After/Same + int timeOffset; // time offset when the animation at index starts. + QAbstractAnimationJob *animation; //points to the animation at timeOffset + }; + + int animationActualTotalDuration(QAbstractAnimationJob *anim) const; + AnimationIndex indexForCurrentTime() const; + + void setCurrentAnimation(QAbstractAnimationJob *anim, bool intermediate = false); + void activateCurrentAnimation(bool intermediate = false); + + void animationInserted(QAbstractAnimationJob *anim); + void animationRemoved(QAbstractAnimationJob *anim,QAbstractAnimationJob*,QAbstractAnimationJob*); + + bool atEnd() const; + + void restart(); + + // handle time changes + void rewindForwards(const AnimationIndex &newAnimationIndex); + void advanceForwards(const AnimationIndex &newAnimationIndex); + + //state + QAbstractAnimationJob *m_currentAnimation; + int m_previousLoop; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QSEQUENTIALANIMATIONGROUPJOB_P_H diff --git a/src/declarative/declarative.pro b/src/declarative/declarative.pro index ca81a4a3d3..6c544bfc3f 100644 --- a/src/declarative/declarative.pro +++ b/src/declarative/declarative.pro @@ -31,3 +31,4 @@ HEADERS += qtdeclarativeversion.h include(util/util.pri) include(qml/qml.pri) include(debugger/debugger.pri) +include(animations/animations.pri)
\ No newline at end of file |