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