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 <QtCore/qthreadstorage.h>
44 #include "private/qabstractanimationjob_p.h"
45 #include "private/qanimationgroupjob_p.h"
46 #include "private/qanimationjobutil_p.h"
48 #define DEFAULT_TIMER_INTERVAL 16
53 Q_GLOBAL_STATIC(QThreadStorage<QQmlAnimationTimer *>, animationTimer)
56 QQmlAnimationTimer::QQmlAnimationTimer() :
57 QAbstractAnimationTimer(), lastTick(0), lastDelta(0),
58 currentAnimationIdx(0), insideTick(false),
59 startAnimationPending(false), stopTimerPending(false),
60 runningLeafAnimations(0)
64 QQmlAnimationTimer *QQmlAnimationTimer::instance(bool create)
66 QQmlAnimationTimer *inst;
68 if (create && !animationTimer()->hasLocalData()) {
69 inst = new QQmlAnimationTimer;
70 animationTimer()->setLocalData(inst);
72 inst = animationTimer() ? animationTimer()->localData() : 0;
75 static QAnimationTimer unifiedTimer;
81 QQmlAnimationTimer *QQmlAnimationTimer::instance()
83 return instance(true);
86 void QQmlAnimationTimer::ensureTimerUpdate()
88 QQmlAnimationTimer *inst = QQmlAnimationTimer::instance(false);
89 QUnifiedTimer *instU = QUnifiedTimer::instance(false);
90 if (instU && inst && inst->isPaused)
91 instU->updateAnimationTimers(-1);
94 void QQmlAnimationTimer::updateAnimationsTime(qint64 delta)
96 //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations
103 //we make sure we only call update time if the time has actually changed
104 //it might happen in some cases that the time doesn't change because events are delayed
105 //when the CPU load is high
108 for (currentAnimationIdx = 0; currentAnimationIdx < animations.count(); ++currentAnimationIdx) {
109 QAbstractAnimationJob *animation = animations.at(currentAnimationIdx);
110 int elapsed = animation->m_totalCurrentTime
111 + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta);
112 animation->setCurrentTime(elapsed);
115 currentAnimationIdx = 0;
119 void QQmlAnimationTimer::updateAnimationTimer()
121 QQmlAnimationTimer *inst = QQmlAnimationTimer::instance(false);
123 inst->restartAnimationTimer();
126 void QQmlAnimationTimer::restartAnimationTimer()
128 if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty())
129 QUnifiedTimer::pauseAnimationTimer(this, closestPauseAnimationTimeToFinish());
131 QUnifiedTimer::resumeAnimationTimer(this);
132 else if (!isRegistered)
133 QUnifiedTimer::startAnimationTimer(this);
136 void QQmlAnimationTimer::startAnimations()
138 startAnimationPending = false;
139 //force timer to update, which prevents large deltas for our newly added animations
140 if (!animations.isEmpty())
141 QUnifiedTimer::instance()->maybeUpdateAnimationsToCurrentTime();
143 //we transfer the waiting animations into the "really running" state
144 animations += animationsToStart;
145 animationsToStart.clear();
146 if (!animations.isEmpty())
147 restartAnimationTimer();
150 void QQmlAnimationTimer::stopTimer()
152 stopTimerPending = false;
153 if (animations.isEmpty()) {
154 QUnifiedTimer::resumeAnimationTimer(this);
155 QUnifiedTimer::stopAnimationTimer(this);
156 // invalidate the start reference time
162 void QQmlAnimationTimer::registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel)
164 if (animation->userControlDisabled())
167 QQmlAnimationTimer *inst = instance(true); //we create the instance if needed
168 inst->registerRunningAnimation(animation);
170 Q_ASSERT(!animation->m_hasRegisteredTimer);
171 animation->m_hasRegisteredTimer = true;
172 inst->animationsToStart << animation;
173 if (!inst->startAnimationPending) {
174 inst->startAnimationPending = true;
175 QMetaObject::invokeMethod(inst, "startAnimations", Qt::QueuedConnection);
180 void QQmlAnimationTimer::unregisterAnimation(QAbstractAnimationJob *animation)
182 QQmlAnimationTimer *inst = QQmlAnimationTimer::instance(false);
184 //at this point the unified timer should have been created
185 //but it might also have been already destroyed in case the application is shutting down
187 inst->unregisterRunningAnimation(animation);
189 if (!animation->m_hasRegisteredTimer)
192 int idx = inst->animations.indexOf(animation);
194 inst->animations.removeAt(idx);
195 // this is needed if we unregister an animation while its running
196 if (idx <= inst->currentAnimationIdx)
197 --inst->currentAnimationIdx;
199 if (inst->animations.isEmpty() && !inst->stopTimerPending) {
200 inst->stopTimerPending = true;
201 QMetaObject::invokeMethod(inst, "stopTimer", Qt::QueuedConnection);
204 inst->animationsToStart.removeOne(animation);
207 animation->m_hasRegisteredTimer = false;
210 void QQmlAnimationTimer::registerRunningAnimation(QAbstractAnimationJob *animation)
212 Q_ASSERT(!animation->userControlDisabled());
214 if (animation->m_isGroup)
217 if (animation->m_isPause) {
218 runningPauseAnimations << animation;
220 runningLeafAnimations++;
223 void QQmlAnimationTimer::unregisterRunningAnimation(QAbstractAnimationJob *animation)
225 if (animation->userControlDisabled())
228 if (animation->m_isGroup)
231 if (animation->m_isPause)
232 runningPauseAnimations.removeOne(animation);
234 runningLeafAnimations--;
235 Q_ASSERT(runningLeafAnimations >= 0);
238 int QQmlAnimationTimer::closestPauseAnimationTimeToFinish()
240 int closestTimeToFinish = INT_MAX;
241 for (int i = 0; i < runningPauseAnimations.size(); ++i) {
242 QAbstractAnimationJob *animation = runningPauseAnimations.at(i);
245 if (animation->direction() == QAbstractAnimationJob::Forward)
246 timeToFinish = animation->duration() - animation->currentLoopTime();
248 timeToFinish = animation->currentLoopTime();
250 if (timeToFinish < closestTimeToFinish)
251 closestTimeToFinish = timeToFinish;
253 return closestTimeToFinish;
256 /////////////////////////////////////////////////////////////////////////////////////////////////////////
258 QAbstractAnimationJob::QAbstractAnimationJob()
261 , m_direction(QAbstractAnimationJob::Forward)
262 , m_state(QAbstractAnimationJob::Stopped)
263 , m_totalCurrentTime(0)
266 , m_uncontrolledFinishTime(-1)
268 , m_previousSibling(0)
270 , m_hasRegisteredTimer(false)
273 , m_disableUserControl(false)
274 , m_hasCurrentTimeChangeListeners(false)
279 QAbstractAnimationJob::~QAbstractAnimationJob()
282 *m_wasDeleted = true;
284 //we can't call stop here. Otherwise we get pure virtual calls
285 if (m_state != Stopped) {
286 State oldState = m_state;
288 stateChanged(oldState, m_state);
290 Q_ASSERT(m_state == Stopped);
291 if (oldState == Running)
292 QQmlAnimationTimer::unregisterAnimation(this);
293 Q_ASSERT(!m_hasRegisteredTimer);
297 m_group->removeAnimation(this);
300 void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState)
302 if (m_state == newState)
305 if (m_loopCount == 0)
308 State oldState = m_state;
309 int oldCurrentTime = m_currentTime;
310 int oldCurrentLoop = m_currentLoop;
311 Direction oldDirection = m_direction;
313 // check if we should Rewind
314 if ((newState == Paused || newState == Running) && oldState == Stopped) {
315 //here we reset the time if needed
316 //we don't call setCurrentTime because this might change the way the animation
317 //behaves: changing the state or changing the current value
318 m_totalCurrentTime = m_currentTime = (m_direction == Forward) ?
319 0 : (m_loopCount == -1 ? duration() : totalDuration());
323 //(un)registration of the animation must always happen before calls to
324 //virtual function (updateState) to ensure a correct state of the timer
325 bool isTopLevel = !m_group || m_group->isStopped();
326 if (oldState == Running) {
327 if (newState == Paused && m_hasRegisteredTimer)
328 QQmlAnimationTimer::ensureTimerUpdate();
329 //the animation, is not running any more
330 QQmlAnimationTimer::unregisterAnimation(this);
331 } else if (newState == Running) {
332 QQmlAnimationTimer::registerAnimation(this, isTopLevel);
335 //starting an animation qualifies as a top level loop change
336 if (newState == Running && oldState == Stopped && !m_group)
337 topLevelAnimationLoopChanged();
339 RETURN_IF_DELETED(updateState(newState, oldState));
341 if (newState != m_state) //this is to be safe if updateState changes the state
344 // Notify state change
345 RETURN_IF_DELETED(stateChanged(newState, oldState));
346 if (newState != m_state) //this is to be safe if updateState changes the state
354 // this ensures that the value is updated now that the animation is running
355 if (oldState == Stopped) {
357 // currentTime needs to be updated if pauseTimer is active
358 RETURN_IF_DELETED(QQmlAnimationTimer::ensureTimerUpdate());
359 RETURN_IF_DELETED(setCurrentTime(m_totalCurrentTime));
365 // Leave running state.
366 int dura = duration();
368 if (dura == -1 || m_loopCount < 0
369 || (oldDirection == Forward && (oldCurrentTime * (oldCurrentLoop + 1)) == (dura * m_loopCount))
370 || (oldDirection == Backward && oldCurrentTime == 0)) {
377 void QAbstractAnimationJob::setDirection(Direction direction)
379 if (m_direction == direction)
382 if (m_state == Stopped) {
383 if (m_direction == Backward) {
384 m_currentTime = duration();
385 m_currentLoop = m_loopCount - 1;
392 // the commands order below is important: first we need to setCurrentTime with the old direction,
393 // then update the direction on this and all children and finally restart the pauseTimer if needed
394 if (m_hasRegisteredTimer)
395 QQmlAnimationTimer::ensureTimerUpdate();
397 m_direction = direction;
398 updateDirection(direction);
400 if (m_hasRegisteredTimer)
401 // needed to update the timer interval in case of a pause animation
402 QQmlAnimationTimer::updateAnimationTimer();
405 void QAbstractAnimationJob::setLoopCount(int loopCount)
407 m_loopCount = loopCount;
410 int QAbstractAnimationJob::totalDuration() const
412 int dura = duration();
415 int loopcount = loopCount();
418 return dura * loopcount;
421 void QAbstractAnimationJob::setCurrentTime(int msecs)
423 msecs = qMax(msecs, 0);
424 // Calculate new time and loop.
425 int dura = duration();
426 int totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount);
428 msecs = qMin(totalDura, msecs);
429 m_totalCurrentTime = msecs;
431 // Update new values.
432 int oldLoop = m_currentLoop;
433 m_currentLoop = ((dura <= 0) ? 0 : (msecs / dura));
434 if (m_currentLoop == m_loopCount) {
436 m_currentTime = qMax(0, dura);
437 m_currentLoop = qMax(0, m_loopCount - 1);
439 if (m_direction == Forward) {
440 m_currentTime = (dura <= 0) ? msecs : (msecs % dura);
442 m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1;
443 if (m_currentTime == dura)
448 if (m_currentLoop != oldLoop && !m_group) //### verify Running as well?
449 topLevelAnimationLoopChanged();
451 RETURN_IF_DELETED(updateCurrentTime(m_currentTime));
453 if (m_currentLoop != oldLoop)
454 currentLoopChanged();
456 // All animations are responsible for stopping the animation when their
457 // own end state is reached; in this case the animation is time driven,
458 // and has reached the end.
459 if ((m_direction == Forward && m_totalCurrentTime == totalDura)
460 || (m_direction == Backward && m_totalCurrentTime == 0)) {
464 if (m_hasCurrentTimeChangeListeners)
465 currentTimeChanged(m_currentTime);
468 void QAbstractAnimationJob::start()
470 if (m_state == Running)
475 void QAbstractAnimationJob::stop()
477 if (m_state == Stopped)
482 void QAbstractAnimationJob::pause()
484 if (m_state == Stopped) {
485 qWarning("QAbstractAnimationJob::pause: Cannot pause a stopped animation");
492 void QAbstractAnimationJob::resume()
494 if (m_state != Paused) {
495 qWarning("QAbstractAnimationJob::resume: "
496 "Cannot resume an animation that is not paused");
502 void QAbstractAnimationJob::setEnableUserControl()
504 m_disableUserControl = false;
507 bool QAbstractAnimationJob::userControlDisabled() const
509 return m_disableUserControl;
512 void QAbstractAnimationJob::setDisableUserControl()
514 m_disableUserControl = true;
519 void QAbstractAnimationJob::updateState(QAbstractAnimationJob::State newState,
520 QAbstractAnimationJob::State oldState)
526 void QAbstractAnimationJob::updateDirection(QAbstractAnimationJob::Direction direction)
531 void QAbstractAnimationJob::finished()
533 //TODO: update this code so it is valid to delete the animation in animationFinished
534 for (int i = 0; i < changeListeners.count(); ++i) {
535 const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
536 if (change.types & QAbstractAnimationJob::Completion) {
537 RETURN_IF_DELETED(change.listener->animationFinished(this));
541 if (m_group && (duration() == -1 || loopCount() < 0)) {
542 //this is an uncontrolled animation, need to notify the group animation we are finished
543 m_group->uncontrolledAnimationFinished(this);
547 void QAbstractAnimationJob::stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
549 for (int i = 0; i < changeListeners.count(); ++i) {
550 const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
551 if (change.types & QAbstractAnimationJob::StateChange) {
552 RETURN_IF_DELETED(change.listener->animationStateChanged(this, newState, oldState));
557 void QAbstractAnimationJob::currentLoopChanged()
559 for (int i = 0; i < changeListeners.count(); ++i) {
560 const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
561 if (change.types & QAbstractAnimationJob::CurrentLoop) {
562 RETURN_IF_DELETED(change.listener->animationCurrentLoopChanged(this));
567 void QAbstractAnimationJob::currentTimeChanged(int currentTime)
569 Q_ASSERT(m_hasCurrentTimeChangeListeners);
571 for (int i = 0; i < changeListeners.count(); ++i) {
572 const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
573 if (change.types & QAbstractAnimationJob::CurrentTime) {
574 RETURN_IF_DELETED(change.listener->animationCurrentTimeChanged(this, currentTime));
579 void QAbstractAnimationJob::addAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
581 if (changes & QAbstractAnimationJob::CurrentTime)
582 m_hasCurrentTimeChangeListeners = true;
584 changeListeners.append(ChangeListener(listener, changes));
587 void QAbstractAnimationJob::removeAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
589 m_hasCurrentTimeChangeListeners = false;
591 changeListeners.removeOne(ChangeListener(listener, changes));
593 for (int i = 0; i < changeListeners.count(); ++i) {
594 const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
595 if (change.types & QAbstractAnimationJob::CurrentTime) {
596 m_hasCurrentTimeChangeListeners = true;
604 //#include "moc_qabstractanimation2_p.cpp"