blob: d98546122f30f6cd8e42cc91ab180bcb3064f690 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "private/qsequentialanimationgroupjob_p.h"
#include "private/qpauseanimationjob_p.h"
#include "private/qanimationjobutil_p.h"
QT_BEGIN_NAMESPACE
QSequentialAnimationGroupJob::QSequentialAnimationGroupJob()
: QAnimationGroupJob()
, m_currentAnimation(nullptr)
, 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) {
int done = uncontrolledAnimationFinishTime(anim);
// If the animation has reached the end, use the uncontrolledFinished value.
if (done >= 0 && (anim->loopCount() - 1 == anim->currentLoop() || anim->state() == Stopped))
return done;
}
return ret;
}
QSequentialAnimationGroupJob::AnimationIndex QSequentialAnimationGroupJob::indexForCurrentTime() const
{
Q_ASSERT(firstChild());
AnimationIndex ret;
QAbstractAnimationJob *anim = nullptr;
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()) {
RETURN_IF_DELETED(setCurrentAnimation(anim, true));
RETURN_IF_DELETED(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
RETURN_IF_DELETED(activateCurrentAnimation());
} else {
RETURN_IF_DELETED(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,
RETURN_IF_DELETED(setCurrentAnimation(anim, true));
RETURN_IF_DELETED(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()) {
RETURN_IF_DELETED(setCurrentAnimation(anim, true));
RETURN_IF_DELETED(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
RETURN_IF_DELETED(activateCurrentAnimation());
} else {
RETURN_IF_DELETED(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()) {
RETURN_IF_DELETED(setCurrentAnimation(anim, true));
RETURN_IF_DELETED(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::clear()
{
m_previousLoop = 0;
QAnimationGroupJob::clear();
// clear() should call removeAnimation(), which will clear m_currentAnimation, eventually.
Q_ASSERT(m_currentAnimation == nullptr);
}
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
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
RETURN_IF_DELETED(rewindForwards(newAnimationIndex));
}
RETURN_IF_DELETED(setCurrentAnimation(newAnimationIndex.animation));
const int newCurrentTime = currentTime - newAnimationIndex.timeOffset;
if (m_currentAnimation) {
RETURN_IF_DELETED(m_currentAnimation->setCurrentTime(newCurrentTime));
if (atEnd()) {
//we make sure that we don't exceed the duration here
m_currentTime += m_currentAnimation->currentTime() - newCurrentTime;
RETURN_IF_DELETED(stop());
}
} else {
//the only case where currentAnimation could be null
//is when all animations have been removed
Q_ASSERT(!firstChild());
m_currentTime = 0;
RETURN_IF_DELETED(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 = nullptr;
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);
RETURN_IF_DELETED(m_currentAnimation->start());
if (!intermediate && isPaused())
m_currentAnimation->pause();
}
void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
{
Q_UNUSED(animation);
Q_ASSERT(animation == m_currentAnimation);
setUncontrolledAnimationFinishTime(m_currentAnimation, m_currentAnimation->currentTime());
int totalTime = currentTime();
if (m_direction == Forward) {
// set the current animation to be the next one
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
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)
{
if (m_currentAnimation == nullptr)
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(nullptr);
}
// duration of the previous animations up to the current animation
m_currentTime = 0;
for (QAbstractAnimationJob *job = firstChild(); job; job = job->nextSibling()) {
if (job == m_currentAnimation)
break;
m_currentTime += animationActualTotalDuration(job);
}
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();
}
void QSequentialAnimationGroupJob::debugAnimation(QDebug d) const
{
d << "SequentialAnimationGroupJob(" << hex << (const void *) this << dec << ")" << "currentAnimation:" << (void *)m_currentAnimation;
debugChildren(d);
}
QT_END_NAMESPACE