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 | |
33 | QT_BEGIN_NAMESPACE |
34 | |
35 | typedef QList<QAbstractAnimation *>::ConstIterator AnimationListConstIt; |
36 | typedef QHash<QAbstractAnimation*, int>::Iterator AnimationTimeHashIt; |
37 | typedef QHash<QAbstractAnimation*, int>::ConstIterator AnimationTimeHashConstIt; |
38 | |
39 | /*! |
40 | Constructs a QParallelAnimationGroup. |
41 | \a parent is passed to QObject's constructor. |
42 | */ |
43 | QParallelAnimationGroup::QParallelAnimationGroup(QObject *parent) |
44 | : QAnimationGroup(*new QParallelAnimationGroupPrivate, parent) |
45 | { |
46 | } |
47 | |
48 | /*! |
49 | \internal |
50 | */ |
51 | QParallelAnimationGroup::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 | */ |
60 | QParallelAnimationGroup::~QParallelAnimationGroup() |
61 | { |
62 | } |
63 | |
64 | /*! |
65 | \reimp |
66 | */ |
67 | int 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 | */ |
86 | void 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 | */ |
143 | void 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 | |
175 | void 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 | |
204 | void 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 | |
212 | void 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 | |
223 | bool 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 | |
236 | void 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 | |
253 | bool QParallelAnimationGroupPrivate::isUncontrolledAnimationFinished(QAbstractAnimation *anim) const |
254 | { |
255 | return uncontrolledFinishTime.value(key: anim, defaultValue: -1) >= 0; |
256 | } |
257 | |
258 | void 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 | */ |
268 | void 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 | */ |
290 | bool QParallelAnimationGroup::event(QEvent *event) |
291 | { |
292 | return QAnimationGroup::event(event); |
293 | } |
294 | |
295 | QT_END_NAMESPACE |
296 | |
297 | #include "moc_qparallelanimationgroup.cpp" |
298 | |