1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 /*!
41     \class QSequentialAnimationGroup
42     \inmodule QtCore
43     \brief The QSequentialAnimationGroup class provides a sequential group of animations.
44     \since 4.6
45     \ingroup animation
46 
47     QSequentialAnimationGroup is a QAnimationGroup that runs its
48     animations in sequence, i.e., it starts one animation after
49     another has finished playing. The animations are played in the
50     order they are added to the group (using
51     \l{QAnimationGroup::}{addAnimation()} or
52     \l{QAnimationGroup::}{insertAnimation()}). The animation group
53     finishes when its last animation has finished.
54 
55     At each moment there is at most one animation that is active in
56     the group; it is returned by currentAnimation(). An empty group
57     has no current animation.
58 
59     A sequential animation group can be treated as any other
60     animation, i.e., it can be started, stopped, and added to other
61     groups. You can also call addPause() or insertPause() to add a
62     pause to a sequential animation group.
63 
64     \snippet code/src_corelib_animation_qsequentialanimationgroup.cpp 0
65 
66     In this example, \c anim1 and \c anim2 are two already set up
67     \l{QPropertyAnimation}s.
68 
69     \sa QAnimationGroup, QAbstractAnimation, {The Animation Framework}
70 */
71 
72 #include "qsequentialanimationgroup.h"
73 #include "qsequentialanimationgroup_p.h"
74 
75 #include "qpauseanimation.h"
76 
77 #include <QtCore/qdebug.h>
78 
79 QT_BEGIN_NAMESPACE
80 
81 typedef QList<QAbstractAnimation *>::ConstIterator AnimationListConstIt;
82 
atEnd() const83 bool QSequentialAnimationGroupPrivate::atEnd() const
84 {
85     // we try to detect if we're at the end of the group
86     //this is true if the following conditions are true:
87     // 1. we're in the last loop
88     // 2. the direction is forward
89     // 3. the current animation is the last one
90     // 4. the current animation has reached its end
91     const int animTotalCurrentTime = QAbstractAnimationPrivate::get(currentAnimation)->totalCurrentTime;
92     return (currentLoop == loopCount - 1
93         && direction == QAbstractAnimation::Forward
94         && currentAnimation == animations.last()
95         && animTotalCurrentTime == animationActualTotalDuration(currentAnimationIndex));
96 }
97 
animationActualTotalDuration(int index) const98 int QSequentialAnimationGroupPrivate::animationActualTotalDuration(int index) const
99 {
100     QAbstractAnimation *anim = animations.at(index);
101     int ret = anim->totalDuration();
102     if (ret == -1 && actualDuration.size() > index)
103         ret = actualDuration.at(index); //we can try the actual duration there
104     return ret;
105 }
106 
indexForCurrentTime() const107 QSequentialAnimationGroupPrivate::AnimationIndex QSequentialAnimationGroupPrivate::indexForCurrentTime() const
108 {
109     Q_ASSERT(!animations.isEmpty());
110 
111     AnimationIndex ret;
112     int duration = 0;
113 
114     for (int i = 0; i < animations.size(); ++i) {
115         duration = animationActualTotalDuration(i);
116 
117         // 'animation' is the current animation if one of these reasons is true:
118         // 1. it's duration is undefined
119         // 2. it ends after msecs
120         // 3. it is the last animation (this can happen in case there is at least 1 uncontrolled animation)
121         // 4. it ends exactly in msecs and the direction is backwards
122         if (duration == -1 || currentTime < (ret.timeOffset + duration)
123             || (currentTime == (ret.timeOffset + duration) && direction == QAbstractAnimation::Backward)) {
124             ret.index = i;
125             return ret;
126         }
127 
128         // 'animation' has a non-null defined duration and is not the one at time 'msecs'.
129         ret.timeOffset += duration;
130     }
131 
132     // this can only happen when one of those conditions is true:
133     // 1. the duration of the group is undefined and we passed its actual duration
134     // 2. there are only 0-duration animations in the group
135     ret.timeOffset -= duration;
136     ret.index = animations.size() - 1;
137     return ret;
138 }
139 
restart()140 void QSequentialAnimationGroupPrivate::restart()
141 {
142     // restarting the group by making the first/last animation the current one
143     if (direction == QAbstractAnimation::Forward) {
144         lastLoop = 0;
145         if (currentAnimationIndex == 0)
146             activateCurrentAnimation();
147         else
148             setCurrentAnimation(0);
149     } else { // direction == QAbstractAnimation::Backward
150         lastLoop = loopCount - 1;
151         int index = animations.size() - 1;
152         if (currentAnimationIndex == index)
153             activateCurrentAnimation();
154         else
155             setCurrentAnimation(index);
156     }
157 }
158 
159 /*!
160     \internal
161     This manages advancing the execution of a group running forwards (time has gone forward),
162     which is the same behaviour for rewinding the execution of a group running backwards
163     (time has gone backward).
164 */
advanceForwards(const AnimationIndex & newAnimationIndex)165 void QSequentialAnimationGroupPrivate::advanceForwards(const AnimationIndex &newAnimationIndex)
166 {
167     if (lastLoop < currentLoop) {
168         // we need to fast forward to the end
169         for (int i = currentAnimationIndex; i < animations.size(); ++i) {
170             QAbstractAnimation *anim = animations.at(i);
171             setCurrentAnimation(i, true);
172             anim->setCurrentTime(animationActualTotalDuration(i));
173         }
174         // this will make sure the current animation is reset to the beginning
175         if (animations.size() == 1)
176             // we need to force activation because setCurrentAnimation will have no effect
177             activateCurrentAnimation();
178         else
179             setCurrentAnimation(0, true);
180     }
181 
182     // and now we need to fast forward from the current position to
183     for (int i = currentAnimationIndex; i < newAnimationIndex.index; ++i) {     //### WRONG,
184         QAbstractAnimation *anim = animations.at(i);
185         setCurrentAnimation(i, true);
186         anim->setCurrentTime(animationActualTotalDuration(i));
187     }
188     // setting the new current animation will happen later
189 }
190 
191 /*!
192     \internal
193     This manages rewinding the execution of a group running forwards (time has gone forward),
194     which is the same behaviour for advancing the execution of a group running backwards
195     (time has gone backward).
196 */
rewindForwards(const AnimationIndex & newAnimationIndex)197 void QSequentialAnimationGroupPrivate::rewindForwards(const AnimationIndex &newAnimationIndex)
198 {
199     if (lastLoop > currentLoop) {
200         // we need to fast rewind to the beginning
201         for (int i = currentAnimationIndex; i >= 0 ; --i) {
202             QAbstractAnimation *anim = animations.at(i);
203             setCurrentAnimation(i, true);
204             anim->setCurrentTime(0);
205         }
206         // this will make sure the current animation is reset to the end
207         if (animations.size() == 1)
208             // we need to force activation because setCurrentAnimation will have no effect
209             activateCurrentAnimation();
210         else
211             setCurrentAnimation(animations.count() - 1, true);
212     }
213 
214     // and now we need to fast rewind from the current position to
215     for (int i = currentAnimationIndex; i > newAnimationIndex.index; --i) {
216         QAbstractAnimation *anim = animations.at(i);
217         setCurrentAnimation(i, true);
218         anim->setCurrentTime(0);
219     }
220     // setting the new current animation will happen later
221 }
222 
223 /*!
224     \fn QSequentialAnimationGroup::currentAnimationChanged(QAbstractAnimation *current)
225 
226     QSequentialAnimationGroup emits this signal when currentAnimation
227     has been changed. \a current is the current animation.
228 
229     \sa currentAnimation()
230 */
231 
232 
233 /*!
234     Constructs a QSequentialAnimationGroup.
235     \a parent is passed to QObject's constructor.
236 */
QSequentialAnimationGroup(QObject * parent)237 QSequentialAnimationGroup::QSequentialAnimationGroup(QObject *parent)
238     : QAnimationGroup(*new QSequentialAnimationGroupPrivate, parent)
239 {
240 }
241 
242 /*!
243     \internal
244 */
QSequentialAnimationGroup(QSequentialAnimationGroupPrivate & dd,QObject * parent)245 QSequentialAnimationGroup::QSequentialAnimationGroup(QSequentialAnimationGroupPrivate &dd,
246                                                      QObject *parent)
247     : QAnimationGroup(dd, parent)
248 {
249 }
250 
251 /*!
252     Destroys the animation group. It will also destroy all its animations.
253 */
~QSequentialAnimationGroup()254 QSequentialAnimationGroup::~QSequentialAnimationGroup()
255 {
256 }
257 
258 /*!
259     Adds a pause of \a msecs to this animation group.
260     The pause is considered as a special type of animation, thus
261     \l{QAnimationGroup::animationCount()}{animationCount} will be
262     increased by one.
263 
264     \sa insertPause(), QAnimationGroup::addAnimation()
265 */
addPause(int msecs)266 QPauseAnimation *QSequentialAnimationGroup::addPause(int msecs)
267 {
268     QPauseAnimation *pause = new QPauseAnimation(msecs);
269     addAnimation(pause);
270     return pause;
271 }
272 
273 /*!
274     Inserts a pause of \a msecs milliseconds at \a index in this animation
275     group.
276 
277     \sa addPause(), QAnimationGroup::insertAnimation()
278 */
insertPause(int index,int msecs)279 QPauseAnimation *QSequentialAnimationGroup::insertPause(int index, int msecs)
280 {
281     Q_D(const QSequentialAnimationGroup);
282 
283     if (index < 0 || index > d->animations.size()) {
284         qWarning("QSequentialAnimationGroup::insertPause: index is out of bounds");
285         return nullptr;
286     }
287 
288     QPauseAnimation *pause = new QPauseAnimation(msecs);
289     insertAnimation(index, pause);
290     return pause;
291 }
292 
293 
294 /*!
295     \property QSequentialAnimationGroup::currentAnimation
296     Returns the animation in the current time.
297 */
currentAnimation() const298 QAbstractAnimation *QSequentialAnimationGroup::currentAnimation() const
299 {
300     Q_D(const QSequentialAnimationGroup);
301     return d->currentAnimation;
302 }
303 
304 /*!
305     \reimp
306 */
duration() const307 int QSequentialAnimationGroup::duration() const
308 {
309     Q_D(const QSequentialAnimationGroup);
310     int ret = 0;
311 
312     for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
313         const int currentDuration = (*it)->totalDuration();
314         if (currentDuration == -1)
315             return -1; // Undetermined length
316 
317         ret += currentDuration;
318     }
319 
320     return ret;
321 }
322 
323 /*!
324     \reimp
325 */
updateCurrentTime(int currentTime)326 void QSequentialAnimationGroup::updateCurrentTime(int currentTime)
327 {
328     Q_D(QSequentialAnimationGroup);
329     if (!d->currentAnimation)
330         return;
331 
332     const QSequentialAnimationGroupPrivate::AnimationIndex newAnimationIndex = d->indexForCurrentTime();
333 
334     // remove unneeded animations from actualDuration list
335     while (newAnimationIndex.index < d->actualDuration.size())
336         d->actualDuration.removeLast();
337 
338     // newAnimationIndex.index is the new current animation
339     if (d->lastLoop < d->currentLoop
340         || (d->lastLoop == d->currentLoop && d->currentAnimationIndex < newAnimationIndex.index)) {
341             // advancing with forward direction is the same as rewinding with backwards direction
342             d->advanceForwards(newAnimationIndex);
343     } else if (d->lastLoop > d->currentLoop
344         || (d->lastLoop == d->currentLoop && d->currentAnimationIndex > newAnimationIndex.index)) {
345             // rewinding with forward direction is the same as advancing with backwards direction
346             d->rewindForwards(newAnimationIndex);
347     }
348 
349     d->setCurrentAnimation(newAnimationIndex.index);
350 
351     const int newCurrentTime = currentTime - newAnimationIndex.timeOffset;
352 
353     if (d->currentAnimation) {
354         d->currentAnimation->setCurrentTime(newCurrentTime);
355         if (d->atEnd()) {
356             //we make sure that we don't exceed the duration here
357             d->currentTime += QAbstractAnimationPrivate::get(d->currentAnimation)->totalCurrentTime - newCurrentTime;
358             stop();
359         }
360     } else {
361         //the only case where currentAnimation could be null
362         //is when all animations have been removed
363         Q_ASSERT(d->animations.isEmpty());
364         d->currentTime = 0;
365         stop();
366     }
367 
368     d->lastLoop = d->currentLoop;
369 }
370 
371 /*!
372     \reimp
373 */
updateState(QAbstractAnimation::State newState,QAbstractAnimation::State oldState)374 void QSequentialAnimationGroup::updateState(QAbstractAnimation::State newState,
375                                             QAbstractAnimation::State oldState)
376 {
377     Q_D(QSequentialAnimationGroup);
378     QAnimationGroup::updateState(newState, oldState);
379 
380     if (!d->currentAnimation)
381         return;
382 
383     switch (newState) {
384     case Stopped:
385         d->currentAnimation->stop();
386         break;
387     case Paused:
388         if (oldState == d->currentAnimation->state()
389             && oldState == QSequentialAnimationGroup::Running) {
390                 d->currentAnimation->pause();
391             }
392         else
393             d->restart();
394         break;
395     case Running:
396         if (oldState == d->currentAnimation->state()
397             && oldState == QSequentialAnimationGroup::Paused)
398             d->currentAnimation->start();
399         else
400             d->restart();
401         break;
402     }
403 }
404 
405 /*!
406     \reimp
407 */
updateDirection(QAbstractAnimation::Direction direction)408 void QSequentialAnimationGroup::updateDirection(QAbstractAnimation::Direction direction)
409 {
410     Q_D(QSequentialAnimationGroup);
411     // we need to update the direction of the current animation
412     if (state() != Stopped && d->currentAnimation)
413         d->currentAnimation->setDirection(direction);
414 }
415 
416 /*!
417     \reimp
418 */
event(QEvent * event)419 bool QSequentialAnimationGroup::event(QEvent *event)
420 {
421     return QAnimationGroup::event(event);
422 }
423 
setCurrentAnimation(int index,bool intermediate)424 void QSequentialAnimationGroupPrivate::setCurrentAnimation(int index, bool intermediate)
425 {
426     Q_Q(QSequentialAnimationGroup);
427 
428     index = qMin(index, animations.count() - 1);
429 
430     if (index == -1) {
431         Q_ASSERT(animations.isEmpty());
432         currentAnimationIndex = -1;
433         currentAnimation = nullptr;
434         return;
435     }
436 
437     // need these two checks below because this func can be called after the current animation
438     // has been removed
439     if (index == currentAnimationIndex && animations.at(index) == currentAnimation)
440         return;
441 
442     // stop the old current animation
443     if (currentAnimation)
444         currentAnimation->stop();
445 
446     currentAnimation = animations.at(index);
447     currentAnimationIndex = index;
448 
449     emit q->currentAnimationChanged(currentAnimation);
450 
451     activateCurrentAnimation(intermediate);
452 }
453 
activateCurrentAnimation(bool intermediate)454 void QSequentialAnimationGroupPrivate::activateCurrentAnimation(bool intermediate)
455 {
456     if (!currentAnimation || state == QSequentialAnimationGroup::Stopped)
457         return;
458 
459     currentAnimation->stop();
460 
461     // we ensure the direction is consistent with the group's direction
462     currentAnimation->setDirection(direction);
463 
464     // connects to the finish signal of uncontrolled animations
465     if (currentAnimation->totalDuration() == -1)
466         connectUncontrolledAnimation(currentAnimation);
467 
468     currentAnimation->start();
469     if (!intermediate && state == QSequentialAnimationGroup::Paused)
470         currentAnimation->pause();
471 }
472 
_q_uncontrolledAnimationFinished()473 void QSequentialAnimationGroupPrivate::_q_uncontrolledAnimationFinished()
474 {
475     Q_Q(QSequentialAnimationGroup);
476     Q_ASSERT(qobject_cast<QAbstractAnimation *>(q->sender()) == currentAnimation);
477 
478     // we trust the duration returned by the animation
479     while (actualDuration.size() < (currentAnimationIndex + 1))
480         actualDuration.append(-1);
481     actualDuration[currentAnimationIndex] = currentAnimation->currentTime();
482 
483     disconnectUncontrolledAnimation(currentAnimation);
484 
485     if ((direction == QAbstractAnimation::Forward && currentAnimation == animations.last())
486         || (direction == QAbstractAnimation::Backward && currentAnimationIndex == 0)) {
487         // we don't handle looping of a group with undefined duration
488         q->stop();
489     } else if (direction == QAbstractAnimation::Forward) {
490         // set the current animation to be the next one
491         setCurrentAnimation(currentAnimationIndex + 1);
492     } else {
493         // set the current animation to be the previous one
494         setCurrentAnimation(currentAnimationIndex - 1);
495     }
496 }
497 
498 /*!
499     \internal
500     This method is called whenever an animation is added to
501     the group at index \a index.
502     Note: We only support insertion after the current animation
503 */
animationInsertedAt(int index)504 void QSequentialAnimationGroupPrivate::animationInsertedAt(int index)
505 {
506     if (currentAnimation == nullptr)
507         setCurrentAnimation(0); // initialize the current animation
508 
509     if (currentAnimationIndex == index
510         && currentAnimation->currentTime() == 0 && currentAnimation->currentLoop() == 0) {
511             //in this case we simply insert an animation before the current one has actually started
512             setCurrentAnimation(index);
513     }
514 
515     //we update currentAnimationIndex in case it has changed (the animation pointer is still valid)
516     currentAnimationIndex = animations.indexOf(currentAnimation);
517 
518     if (index < currentAnimationIndex || currentLoop != 0) {
519         qWarning("QSequentialGroup::insertAnimation only supports to add animations after the current one.");
520         return; //we're not affected because it is added after the current one
521     }
522 }
523 
524 /*!
525     \internal
526     This method is called whenever an animation is removed from
527     the group at index \a index. The animation is no more listed when this
528     method is called.
529 */
animationRemoved(int index,QAbstractAnimation * anim)530 void QSequentialAnimationGroupPrivate::animationRemoved(int index, QAbstractAnimation *anim)
531 {
532     Q_Q(QSequentialAnimationGroup);
533     QAnimationGroupPrivate::animationRemoved(index, anim);
534 
535     if (!currentAnimation)
536         return;
537 
538     if (actualDuration.size() > index)
539         actualDuration.removeAt(index);
540 
541     const int currentIndex = animations.indexOf(currentAnimation);
542     if (currentIndex == -1) {
543         //we're removing the current animation
544 
545         disconnectUncontrolledAnimation(currentAnimation);
546 
547         if (index < animations.count())
548             setCurrentAnimation(index); //let's try to take the next one
549         else if (index > 0)
550             setCurrentAnimation(index - 1);
551         else// case all animations were removed
552             setCurrentAnimation(-1);
553     } else if (currentAnimationIndex > index) {
554         currentAnimationIndex--;
555     }
556 
557     // duration of the previous animations up to the current animation
558     currentTime = 0;
559     for (int i = 0; i < currentAnimationIndex; ++i) {
560         const int current = animationActualTotalDuration(i);
561         currentTime += current;
562     }
563 
564     if (currentIndex != -1) {
565         //the current animation is not the one being removed
566         //so we add its current time to the current time of this group
567         currentTime += QAbstractAnimationPrivate::get(currentAnimation)->totalCurrentTime;
568     }
569 
570     //let's also update the total current time
571     totalCurrentTime = currentTime + loopCount * q->duration();
572 }
573 
574 QT_END_NAMESPACE
575 
576 #include "moc_qsequentialanimationgroup.cpp"
577