Support looping for "uncontrolled animations".

The render thread animations rely heavily on uncontrolled
animations, meaning animations with duration=-1. We support
this by adding a m_currentLoopStartTime and incrementally
counting the finish time of each uncontrolled animation.

Change-Id: I1f2ccea09aff4c51b1a7f98a2ddb58636af50557
Reviewed-by: Jan Arve Sæther <jan-arve.saether@digia.com>
This commit is contained in:
Gunnar Sletta 2013-09-20 15:26:03 +02:00 committed by The Qt Project
parent 6d425ebab3
commit 57ae961bcf
9 changed files with 191 additions and 30 deletions

View File

@ -269,6 +269,7 @@ QAbstractAnimationJob::QAbstractAnimationJob()
, m_currentTime(0)
, m_currentLoop(0)
, m_uncontrolledFinishTime(-1)
, m_currentLoopStartTime(0)
, m_nextSibling(0)
, m_previousSibling(0)
, m_wasDeleted(0)
@ -304,6 +305,14 @@ QAbstractAnimationJob::~QAbstractAnimationJob()
m_group->removeAnimation(this);
}
void QAbstractAnimationJob::fireTopLevelAnimationLoopChanged()
{
m_uncontrolledFinishTime = -1;
if (m_group)
m_currentLoopStartTime = 0;
topLevelAnimationLoopChanged();
}
void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState)
{
if (m_state == newState)
@ -324,6 +333,11 @@ void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState)
//behaves: changing the state or changing the current value
m_totalCurrentTime = m_currentTime = (m_direction == Forward) ?
0 : (m_loopCount == -1 ? duration() : totalDuration());
// Reset uncontrolled finish time and currentLoopStartTime for this run.
m_uncontrolledFinishTime = -1;
if (!m_group)
m_currentLoopStartTime = m_totalCurrentTime;
}
m_state = newState;
@ -341,7 +355,7 @@ void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState)
//starting an animation qualifies as a top level loop change
if (newState == Running && oldState == Stopped && !m_group)
topLevelAnimationLoopChanged();
fireTopLevelAnimationLoopChanged();
RETURN_IF_DELETED(updateState(newState, oldState));
@ -430,30 +444,49 @@ 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 totalDura;
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);
if (dura < 0 && m_direction == Forward) {
totalDura = -1;
if (m_uncontrolledFinishTime >= 0 && msecs >= m_uncontrolledFinishTime) {
msecs = m_uncontrolledFinishTime;
if (m_currentLoop == m_loopCount - 1) {
totalDura = m_uncontrolledFinishTime;
} else {
++m_currentLoop;
m_currentLoopStartTime = msecs;
m_uncontrolledFinishTime = -1;
}
}
m_totalCurrentTime = msecs;
m_currentTime = msecs - m_currentLoopStartTime;
} else {
if (m_direction == Forward) {
m_currentTime = (dura <= 0) ? msecs : (msecs % dura);
totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount);
if (totalDura != -1)
msecs = qMin(totalDura, msecs);
m_totalCurrentTime = msecs;
// Update new values.
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 {
m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1;
if (m_currentTime == dura)
--m_currentLoop;
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();
fireTopLevelAnimationLoopChanged();
RETURN_IF_DELETED(updateCurrentTime(m_currentTime));

View File

@ -123,6 +123,8 @@ protected:
virtual void updateDirection(QAbstractAnimationJob::Direction direction);
virtual void topLevelAnimationLoopChanged() {}
void fireTopLevelAnimationLoopChanged();
void setState(QAbstractAnimationJob::State state);
void finished();
@ -143,6 +145,7 @@ protected:
int m_currentLoop;
//records the finish time for an uncontrolled animation (used by animation groups)
int m_uncontrolledFinishTime;
int m_currentLoopStartTime; // used together with m_uncontrolledFinishTime
struct ChangeListener {
ChangeListener(QAnimationJobChangeListener *l, QAbstractAnimationJob::ChangeTypes t) : listener(l), types(t) {}

View File

@ -57,7 +57,7 @@ QAnimationGroupJob::~QAnimationGroupJob()
void QAnimationGroupJob::topLevelAnimationLoopChanged()
{
for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling())
animation->topLevelAnimationLoopChanged();
animation->fireTopLevelAnimationLoopChanged();
}
void QAnimationGroupJob::appendAnimation(QAbstractAnimationJob *animation)

View File

@ -216,11 +216,20 @@ void QParallelAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimatio
return;
int maxDuration = 0;
for (QAbstractAnimationJob *job = firstChild(); job; job = job->nextSibling())
bool running = false;
for (QAbstractAnimationJob *job = firstChild(); job; job = job->nextSibling()) {
if (job->state() == Running)
running = true;
maxDuration = qMax(maxDuration, job->totalDuration());
}
if (m_currentTime >= maxDuration)
setUncontrolledAnimationFinishTime(this, qMax(maxDuration, currentTime()));
if (!running
&& ((m_direction == Forward && m_currentLoop == m_loopCount -1)
|| m_direction == Backward && m_currentLoop == 0)) {
stop();
}
}
QT_END_NAMESPACE

View File

@ -64,6 +64,7 @@ bool QSequentialAnimationGroupJob::atEnd() const
// 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
@ -101,8 +102,9 @@ QSequentialAnimationGroupJob::AnimationIndex QSequentialAnimationGroupJob::index
return ret;
}
if (anim == m_currentAnimation)
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;
@ -211,6 +213,7 @@ void QSequentialAnimationGroupJob::updateCurrentTime(int currentTime)
|| (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && newAnimationIndex.afterCurrent)) {
// advancing with forward direction is the same as rewinding with backwards direction
RETURN_IF_DELETED(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
@ -319,17 +322,41 @@ void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimat
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) {
int totalTime = currentTime();
if (m_direction == Forward) {
// set the current animation to be the next one
setCurrentAnimation(m_currentAnimation->nextSibling());
if (m_currentAnimation->nextSibling())
setCurrentAnimation(m_currentAnimation->nextSibling());
for (QAbstractAnimationJob *a = animation->nextSibling(); a; a = a->nextSibling()) {
int dur = a->duration();
if (dur == -1) {
totalTime = -1;
break;
} else {
totalTime += dur;
}
}
} else {
// set the current animation to be the previous one
setCurrentAnimation(m_currentAnimation->previousSibling());
if (m_currentAnimation->previousSibling())
setCurrentAnimation(m_currentAnimation->previousSibling());
for (QAbstractAnimationJob *a = animation->previousSibling(); a; a = a->previousSibling()) {
int dur = a->duration();
if (dur == -1) {
totalTime = -1;
break;
} else {
totalTime += dur;
}
}
}
if (totalTime >= 0)
setUncontrolledAnimationFinishTime(this, totalTime);
if (atEnd())
stop();
}
void QSequentialAnimationGroupJob::animationInserted(QAbstractAnimationJob *anim)

View File

@ -1921,6 +1921,7 @@ void QQuickBulkValueAnimator::topLevelAnimationLoopChanged()
//check for new from every top-level loop (when the top level animation is started and all subsequent loops)
if (fromSourced)
*fromSourced = false;
QAbstractAnimationJob::topLevelAnimationLoopChanged();
}
/*!

View File

@ -185,7 +185,6 @@ bool QQuickAnimatorProxyJob::event(QEvent *e)
if ((uint) e->type() == QQuickAnimatorController::AnimationFinished) {
// Update the duration of this proxy to the current time and stop it so
// that parent animations can progress gracefully
m_duration = m_currentTime;
stop();
return true;
}

View File

@ -64,6 +64,7 @@ private slots:
void startGroupWithRunningChild();
void zeroDurationAnimation();
void stopUncontrolledAnimations();
void uncontrolledWithLoops();
void loopCount_data();
void loopCount();
void addAndRemoveDuration();
@ -779,6 +780,9 @@ void tst_QParallelAnimationGroupJob::loopCount_data()
}
#undef Stopped
#undef Running
void tst_QParallelAnimationGroupJob::loopCount()
{
QFETCH(bool, directionBackward);
@ -926,6 +930,46 @@ void tst_QParallelAnimationGroupJob::crashWhenRemovingUncontrolledAnimation()
delete anim2;
}
void tst_QParallelAnimationGroupJob::uncontrolledWithLoops()
{
QParallelAnimationGroupJob group;
TestAnimation *plain = new TestAnimation(100);
TestAnimation *loopsForever = new TestAnimation();
UncontrolledAnimation *notTimeBased = new UncontrolledAnimation();
loopsForever->setLoopCount(-1);
group.appendAnimation(plain);
group.appendAnimation(loopsForever);
group.appendAnimation(notTimeBased);
StateChangeListener listener;
group.addAnimationChangeListener(&listener, QAbstractAnimationJob::CurrentLoop);
group.setLoopCount(2);
group.start();
QCOMPARE(group.currentLoop(), 0);
QCOMPARE(group.state(), QAbstractAnimationJob::Running);
QCOMPARE(plain->state(), QAbstractAnimationJob::Running);
QCOMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
QCOMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
loopsForever->stop();
notTimeBased->stop();
QTRY_COMPARE(group.currentLoop(), 1);
QCOMPARE(group.state(), QAbstractAnimationJob::Running);
QCOMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
QCOMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
loopsForever->stop();
notTimeBased->stop();
QTRY_COMPARE(group.state(), QAbstractAnimationJob::Stopped);
}
QTEST_MAIN(tst_QParallelAnimationGroupJob)
#include "tst_qparallelanimationgroupjob.moc"

View File

@ -73,6 +73,7 @@ private slots:
void startGroupWithRunningChild();
void zeroDurationAnimation();
void stopUncontrolledAnimations();
void uncontrolledWithLoops();
void finishWithUncontrolledAnimation();
void addRemoveAnimation();
void currentAnimation();
@ -1613,5 +1614,49 @@ void tst_QSequentialAnimationGroupJob::pauseResume()
anim->removeAnimationChangeListener(&spy, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::uncontrolledWithLoops()
{
QSequentialAnimationGroupJob group;
TestAnimation *plain = new TestAnimation(100);
TestAnimation *loopsForever = new TestAnimation();
UncontrolledAnimation *notTimeBased = new UncontrolledAnimation();
loopsForever->setLoopCount(-1);
group.appendAnimation(plain);
group.appendAnimation(loopsForever);
group.appendAnimation(notTimeBased);
StateChangeListener listener;
group.addAnimationChangeListener(&listener, QAbstractAnimationJob::CurrentLoop);
group.setLoopCount(2);
group.start();
QCOMPARE(group.currentLoop(), 0);
QCOMPARE(group.state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(plain->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
loopsForever->stop();
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Stopped); // Stops on its own after 250ms
QTRY_COMPARE(group.currentLoop(), 1);
QCOMPARE(group.state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(plain->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(plain->state(), QAbstractAnimationJob::Stopped);
QTRY_COMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
loopsForever->stop();
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Stopped);
QTRY_COMPARE(group.state(), QAbstractAnimationJob::Stopped);
}
QTEST_MAIN(tst_QSequentialAnimationGroupJob)
#include "tst_qsequentialanimationgroupjob.moc"