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