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:
parent
6d425ebab3
commit
57ae961bcf
|
@ -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));
|
||||
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue