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
4/*!
5 \class QParallelAnimationGroup
6 \inmodule QtCore
7 \brief The QParallelAnimationGroup class provides a parallel group of animations.
8 \since 4.6
9 \ingroup animation
10
11 QParallelAnimationGroup--a \l{QAnimationGroup}{container for
12 animations}--starts all its animations when it is
13 \l{QAbstractAnimation::start()}{started} itself, i.e., runs all
14 animations in parallel. The animation group finishes when the
15 longest lasting animation has finished.
16
17 You can treat QParallelAnimationGroup as any other QAbstractAnimation,
18 e.g., pause, resume, or add it to other animation groups.
19
20 \snippet code/src_corelib_animation_qparallelanimationgroup.cpp 0
21
22 In this example, \c anim1 and \c anim2 are two
23 \l{QPropertyAnimation}s that have already been set up.
24
25 \sa QAnimationGroup, QPropertyAnimation, {The Animation Framework}
26*/
27
28
29#include "qparallelanimationgroup.h"
30#include "qparallelanimationgroup_p.h"
31//#define QANIMATION_DEBUG
32
33QT_BEGIN_NAMESPACE
34
35typedef QList<QAbstractAnimation *>::ConstIterator AnimationListConstIt;
36typedef QHash<QAbstractAnimation*, int>::Iterator AnimationTimeHashIt;
37typedef QHash<QAbstractAnimation*, int>::ConstIterator AnimationTimeHashConstIt;
38
39/*!
40 Constructs a QParallelAnimationGroup.
41 \a parent is passed to QObject's constructor.
42*/
43QParallelAnimationGroup::QParallelAnimationGroup(QObject *parent)
44 : QAnimationGroup(*new QParallelAnimationGroupPrivate, parent)
45{
46}
47
48/*!
49 \internal
50*/
51QParallelAnimationGroup::QParallelAnimationGroup(QParallelAnimationGroupPrivate &dd,
52 QObject *parent)
53 : QAnimationGroup(dd, parent)
54{
55}
56
57/*!
58 Destroys the animation group. It will also destroy all its animations.
59*/
60QParallelAnimationGroup::~QParallelAnimationGroup()
61{
62}
63
64/*!
65 \reimp
66*/
67int QParallelAnimationGroup::duration() const
68{
69 Q_D(const QParallelAnimationGroup);
70 int ret = 0;
71
72 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
73 const int currentDuration = (*it)->totalDuration();
74 if (currentDuration == -1)
75 return -1; // Undetermined length
76
77 ret = qMax(a: ret, b: currentDuration);
78 }
79
80 return ret;
81}
82
83/*!
84 \reimp
85*/
86void QParallelAnimationGroup::updateCurrentTime(int currentTime)
87{
88 Q_D(QParallelAnimationGroup);
89 if (d->animations.isEmpty())
90 return;
91
92 if (d->currentLoop > d->lastLoop) {
93 // simulate completion of the loop
94 int dura = duration();
95 if (dura > 0) {
96 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
97 QAbstractAnimation *animation = (*it);
98 if (animation->state() != QAbstractAnimation::Stopped)
99 animation->setCurrentTime(dura); // will stop
100 }
101 }
102 } else if (d->currentLoop < d->lastLoop) {
103 // simulate completion of the loop seeking backwards
104 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
105 QAbstractAnimation *animation = *it;
106 //we need to make sure the animation is in the right state
107 //and then rewind it
108 d->applyGroupState(animation);
109 animation->setCurrentTime(0);
110 animation->stop();
111 }
112 }
113
114#ifdef QANIMATION_DEBUG
115 qDebug("QParallellAnimationGroup %5d: setCurrentTime(%d), loop:%d, last:%d, timeFwd:%d, lastcurrent:%d, %d",
116 __LINE__, d->currentTime, d->currentLoop, d->lastLoop, timeFwd, d->lastCurrentTime, state());
117#endif
118 // finally move into the actual time of the current loop
119 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
120 QAbstractAnimation *animation = *it;
121 const int dura = animation->totalDuration();
122 //if the loopcount is bigger we should always start all animations
123 if (d->currentLoop > d->lastLoop
124 //if we're at the end of the animation, we need to start it if it wasn't already started in this loop
125 //this happens in Backward direction where not all animations are started at the same time
126 || d->shouldAnimationStart(animation, startIfAtEnd: d->lastCurrentTime > dura /*startIfAtEnd*/)) {
127 d->applyGroupState(animation);
128 }
129
130 if (animation->state() == state()) {
131 animation->setCurrentTime(currentTime);
132 if (dura > 0 && currentTime > dura)
133 animation->stop();
134 }
135 }
136 d->lastLoop = d->currentLoop;
137 d->lastCurrentTime = currentTime;
138}
139
140/*!
141 \reimp
142*/
143void QParallelAnimationGroup::updateState(QAbstractAnimation::State newState,
144 QAbstractAnimation::State oldState)
145{
146 Q_D(QParallelAnimationGroup);
147 QAnimationGroup::updateState(newState, oldState);
148
149 switch (newState) {
150 case Stopped:
151 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it)
152 (*it)->stop();
153 d->disconnectUncontrolledAnimations();
154 break;
155 case Paused:
156 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
157 if ((*it)->state() == Running)
158 (*it)->pause();
159 }
160 break;
161 case Running:
162 d->connectUncontrolledAnimations();
163 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
164 QAbstractAnimation *animation = *it;
165 if (oldState == Stopped)
166 animation->stop();
167 animation->setDirection(d->direction);
168 if (d->shouldAnimationStart(animation, startIfAtEnd: oldState == Stopped))
169 animation->start();
170 }
171 break;
172 }
173}
174
175void QParallelAnimationGroupPrivate::_q_uncontrolledAnimationFinished()
176{
177 Q_Q(QParallelAnimationGroup);
178
179 QAbstractAnimation *animation = qobject_cast<QAbstractAnimation *>(object: q->sender());
180 Q_ASSERT(animation);
181
182 int uncontrolledRunningCount = 0;
183 if (animation->duration() == -1 || animation->loopCount() < 0) {
184 for (AnimationTimeHashIt it = uncontrolledFinishTime.begin(), cend = uncontrolledFinishTime.end(); it != cend; ++it) {
185 if (it.key() == animation) {
186 *it = animation->currentTime();
187 }
188 if (it.value() == -1)
189 ++uncontrolledRunningCount;
190 }
191 }
192
193 if (uncontrolledRunningCount > 0)
194 return;
195
196 int maxDuration = 0;
197 for (AnimationListConstIt it = animations.constBegin(), cend = animations.constEnd(); it != cend; ++it)
198 maxDuration = qMax(a: maxDuration, b: (*it)->totalDuration());
199
200 if (currentTime >= maxDuration)
201 q->stop();
202}
203
204void QParallelAnimationGroupPrivate::disconnectUncontrolledAnimations()
205{
206 for (AnimationTimeHashConstIt it = uncontrolledFinishTime.constBegin(), cend = uncontrolledFinishTime.constEnd(); it != cend; ++it)
207 disconnectUncontrolledAnimation(anim: it.key());
208
209 uncontrolledFinishTime.clear();
210}
211
212void QParallelAnimationGroupPrivate::connectUncontrolledAnimations()
213{
214 for (AnimationListConstIt it = animations.constBegin(), cend = animations.constEnd(); it != cend; ++it) {
215 QAbstractAnimation *animation = *it;
216 if (animation->duration() == -1 || animation->loopCount() < 0) {
217 uncontrolledFinishTime[animation] = -1;
218 connectUncontrolledAnimation(anim: animation);
219 }
220 }
221}
222
223bool QParallelAnimationGroupPrivate::shouldAnimationStart(QAbstractAnimation *animation, bool startIfAtEnd) const
224{
225 const int dura = animation->totalDuration();
226 if (dura == -1)
227 return !isUncontrolledAnimationFinished(anim: animation);
228 if (startIfAtEnd)
229 return currentTime <= dura;
230 if (direction == QAbstractAnimation::Forward)
231 return currentTime < dura;
232 else //direction == QAbstractAnimation::Backward
233 return currentTime && currentTime <= dura;
234}
235
236void QParallelAnimationGroupPrivate::applyGroupState(QAbstractAnimation *animation)
237{
238 switch (state)
239 {
240 case QAbstractAnimation::Running:
241 animation->start();
242 break;
243 case QAbstractAnimation::Paused:
244 animation->pause();
245 break;
246 case QAbstractAnimation::Stopped:
247 default:
248 break;
249 }
250}
251
252
253bool QParallelAnimationGroupPrivate::isUncontrolledAnimationFinished(QAbstractAnimation *anim) const
254{
255 return uncontrolledFinishTime.value(key: anim, defaultValue: -1) >= 0;
256}
257
258void QParallelAnimationGroupPrivate::animationRemoved(qsizetype index, QAbstractAnimation *anim)
259{
260 QAnimationGroupPrivate::animationRemoved(index, anim);
261 disconnectUncontrolledAnimation(anim);
262 uncontrolledFinishTime.remove(key: anim);
263}
264
265/*!
266 \reimp
267*/
268void QParallelAnimationGroup::updateDirection(QAbstractAnimation::Direction direction)
269{
270 Q_D(QParallelAnimationGroup);
271 //we need to update the direction of the current animation
272 if (state() != Stopped) {
273 for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it)
274 (*it)->setDirection(direction);
275 } else {
276 if (direction == Forward) {
277 d->lastLoop = 0;
278 d->lastCurrentTime = 0;
279 } else {
280 // Looping backwards with loopCount == -1 does not really work well...
281 d->lastLoop = (d->loopCount == -1 ? 0 : d->loopCount - 1);
282 d->lastCurrentTime = duration();
283 }
284 }
285}
286
287/*!
288 \reimp
289*/
290bool QParallelAnimationGroup::event(QEvent *event)
291{
292 return QAnimationGroup::event(event);
293}
294
295QT_END_NAMESPACE
296
297#include "moc_qparallelanimationgroup.cpp"
298

source code of qtbase/src/corelib/animation/qparallelanimationgroup.cpp