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
qsequentialanimationgroupjob.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
4
5#include "private/qsequentialanimationgroupjob_p.h"
6#include "private/qpauseanimationjob_p.h"
7#include "private/qanimationjobutil_p.h"
8
9#include <QtCore/qnumeric.h>
10
11#include <limits>
12
13QT_BEGIN_NAMESPACE
14
15namespace {
16
17/*!
18 \internal
19 Returns INT_MAX in case of overflow
20*/
21int addSaturating(int a, int b)
22{
23 int r;
24 if (qAddOverflow(a, b, &r))
25 return std::numeric_limits<int>::max();
26 return r;
27}
28
29} // namespace
30
31QSequentialAnimationGroupJob::QSequentialAnimationGroupJob()
32 : QAnimationGroupJob()
33 , m_currentAnimation(nullptr)
34 , m_previousLoop(0)
35{
36}
37
38QSequentialAnimationGroupJob::~QSequentialAnimationGroupJob()
39{
40}
41
42bool QSequentialAnimationGroupJob::atEnd() const
43{
44 // we try to detect if we're at the end of the group
45 //this is true if the following conditions are true:
46 // 1. we're in the last loop
47 // 2. the direction is forward
48 // 3. the current animation is the last one
49 // 4. the current animation has reached its end
50
51 const int animTotalCurrentTime = m_currentAnimation->currentTime();
52 return (m_currentLoop == m_loopCount - 1
53 && m_direction == Forward
54 && !m_children.next(m_currentAnimation)
55 && animTotalCurrentTime == animationActualTotalDuration(m_currentAnimation));
56}
57
58int QSequentialAnimationGroupJob::animationActualTotalDuration(
59 const QAbstractAnimationJob *anim) const
60{
61 int ret = anim->totalDuration();
62 if (ret == -1) {
63 int done = uncontrolledAnimationFinishTime(anim);
64 // If the animation has reached the end, use the uncontrolledFinished value.
65 if (done >= 0 && (anim->loopCount() - 1 == anim->currentLoop() || anim->state() == Stopped))
66 return done;
67 }
68 return ret;
69}
70
71QSequentialAnimationGroupJob::AnimationIndex QSequentialAnimationGroupJob::indexForCurrentTime() const
72{
73 Q_ASSERT(!m_children.isEmpty());
74
75 AnimationIndex ret;
76 int duration = 0;
77
78 for (const QAbstractAnimationJob *anim : m_children) {
79 duration = animationActualTotalDuration(anim);
80
81 // 'animation' is the current animation if one of these reasons is true:
82 // 1. it's duration is undefined
83 // 2. it ends after msecs
84 // 3. it is the last animation (this can happen in case there is at least 1 uncontrolled animation)
85 // 4. it ends exactly in msecs and the direction is backwards
86 const int offsetPlusDuration = addSaturating(ret.timeOffset, duration);
87 if (duration == -1 || m_currentTime < offsetPlusDuration
88 || (m_currentTime == offsetPlusDuration && m_direction == QAbstractAnimationJob::Backward)) {
89 ret.animation = anim;
90 return ret;
91 }
92
93 if (anim == m_currentAnimation) {
94 ret.afterCurrent = true;
95 }
96
97 // 'animation' has a non-null defined duration and is not the one at time 'msecs'.
98 ret.timeOffset = offsetPlusDuration;
99 }
100
101 // this can only happen when one of those conditions is true:
102 // 1. the duration of the group is undefined and we passed its actual duration
103 // 2. there are only 0-duration animations in the group
104 ret.timeOffset -= duration;
105 ret.animation = m_children.last();
106 return ret;
107}
108
109void QSequentialAnimationGroupJob::restart()
110{
111 // restarting the group by making the first/last animation the current one
112 if (m_direction == Forward) {
113 m_previousLoop = 0;
114 if (m_currentAnimation == m_children.first())
115 activateCurrentAnimation();
116 else
117 setCurrentAnimation(m_children.first());
118 }
119 else { // direction == Backward
120 m_previousLoop = m_loopCount - 1;
121 if (m_currentAnimation == m_children.last())
122 activateCurrentAnimation();
123 else
124 setCurrentAnimation(m_children.last());
125 }
126}
127
128void QSequentialAnimationGroupJob::advanceForwards(const AnimationIndex &newAnimationIndex)
129{
130 if (m_previousLoop < m_currentLoop) {
131 // we need to fast forward to the end
132 for (QAbstractAnimationJob *anim = m_currentAnimation; anim; anim = m_children.next(anim)) {
133 RETURN_IF_DELETED(setCurrentAnimation(anim, true));
134 RETURN_IF_DELETED(anim->setCurrentTime(animationActualTotalDuration(anim)));
135 }
136 // this will make sure the current animation is reset to the beginning
137 if (m_children.count() == 1) {
138 // we need to force activation because setCurrentAnimation will have no effect
139 RETURN_IF_DELETED(activateCurrentAnimation());
140 } else {
141 RETURN_IF_DELETED(setCurrentAnimation(m_children.first(), true));
142 }
143 }
144
145 // and now we need to fast forward from the current position to
146 for (QAbstractAnimationJob *anim = m_currentAnimation;
147 anim && anim != newAnimationIndex.animation; anim = m_children.next(anim)) { //### WRONG,
148 RETURN_IF_DELETED(setCurrentAnimation(anim, true));
149 RETURN_IF_DELETED(anim->setCurrentTime(animationActualTotalDuration(anim)));
150 }
151 // setting the new current animation will happen later
152}
153
154void QSequentialAnimationGroupJob::rewindForwards(const AnimationIndex &newAnimationIndex)
155{
156 if (m_previousLoop > m_currentLoop) {
157 // we need to fast rewind to the beginning
158 for (QAbstractAnimationJob *anim = m_currentAnimation; anim;
159 anim = m_children.prev(anim)) {
160 RETURN_IF_DELETED(setCurrentAnimation(anim, true));
161 RETURN_IF_DELETED(anim->setCurrentTime(0));
162 }
163 // this will make sure the current animation is reset to the end
164 if (m_children.count() == 1) { //count == 1
165 // we need to force activation because setCurrentAnimation will have no effect
166 RETURN_IF_DELETED(activateCurrentAnimation());
167 } else {
168 RETURN_IF_DELETED(setCurrentAnimation(m_children.last(), true));
169 }
170 }
171
172 // and now we need to fast rewind from the current position to
173 for (QAbstractAnimationJob *anim = m_currentAnimation;
174 anim && anim != newAnimationIndex.animation; anim = m_children.prev(anim)) {
175 RETURN_IF_DELETED(setCurrentAnimation(anim, true));
176 RETURN_IF_DELETED(anim->setCurrentTime(0));
177 }
178 // setting the new current animation will happen later
179}
180
181int QSequentialAnimationGroupJob::duration() const
182{
183 int ret = 0;
184
185 for (const QAbstractAnimationJob *anim : m_children) {
186 const int currentDuration = anim->totalDuration();
187 if (currentDuration == -1)
188 return -1; // Undetermined length
189
190 if (qAddOverflow(ret, currentDuration, &ret))
191 return std::numeric_limits<int>::max();
192 }
193
194 return ret;
195}
196
197void QSequentialAnimationGroupJob::clear()
198{
199 m_previousLoop = 0;
200 QAnimationGroupJob::clear();
201
202 // clear() should call removeAnimation(), which will clear m_currentAnimation, eventually.
203 Q_ASSERT(m_currentAnimation == nullptr);
204}
205
206void QSequentialAnimationGroupJob::updateCurrentTime(int currentTime)
207{
208 if (!m_currentAnimation)
209 return;
210
211 const QSequentialAnimationGroupJob::AnimationIndex newAnimationIndex = indexForCurrentTime();
212
213 // newAnimationIndex.index is the new current animation
214 if (m_previousLoop < m_currentLoop
215 || (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && newAnimationIndex.afterCurrent)) {
216 // advancing with forward direction is the same as rewinding with backwards direction
217 RETURN_IF_DELETED(advanceForwards(newAnimationIndex));
218
219 } else if (m_previousLoop > m_currentLoop
220 || (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && !newAnimationIndex.afterCurrent)) {
221 // rewinding with forward direction is the same as advancing with backwards direction
222 RETURN_IF_DELETED(rewindForwards(newAnimationIndex));
223 }
224
225 RETURN_IF_DELETED(setCurrentAnimation(newAnimationIndex.animation));
226
227 const int newCurrentTime = currentTime - newAnimationIndex.timeOffset;
228
229 if (m_currentAnimation) {
230 RETURN_IF_DELETED(m_currentAnimation->setCurrentTime(newCurrentTime));
231 if (atEnd()) {
232 //we make sure that we don't exceed the duration here
233 const int diff = m_currentAnimation->currentTime() - newCurrentTime;
234 m_currentTime = addSaturating(m_currentTime, diff);
235 RETURN_IF_DELETED(stop());
236 }
237 } else {
238 //the only case where currentAnimation could be null
239 //is when all animations have been removed
240 Q_ASSERT(m_children.isEmpty());
241 m_currentTime = 0;
242 RETURN_IF_DELETED(stop());
243 }
244
245 m_previousLoop = m_currentLoop;
246}
247
248void QSequentialAnimationGroupJob::updateState(QAbstractAnimationJob::State newState,
249 QAbstractAnimationJob::State oldState)
250{
251 QAnimationGroupJob::updateState(newState, oldState);
252
253 if (!m_currentAnimation)
254 return;
255
256 switch (newState) {
257 case Stopped:
258 m_currentAnimation->stop();
259 break;
260 case Paused:
261 if (oldState == m_currentAnimation->state() && oldState == Running)
262 m_currentAnimation->pause();
263 else
264 restart();
265 break;
266 case Running:
267 if (oldState == m_currentAnimation->state() && oldState == Paused)
268 m_currentAnimation->start();
269 else
270 restart();
271 break;
272 }
273}
274
275void QSequentialAnimationGroupJob::updateDirection(QAbstractAnimationJob::Direction direction)
276{
277 // we need to update the direction of the current animation
278 if (!isStopped() && m_currentAnimation)
279 m_currentAnimation->setDirection(direction);
280}
281
282void QSequentialAnimationGroupJob::setCurrentAnimation(
283 const QAbstractAnimationJob *anim, bool intermediate)
284{
285 if (!anim) {
286 Q_ASSERT(m_children.isEmpty());
287 m_currentAnimation = nullptr;
288 return;
289 }
290
291 if (anim == m_currentAnimation)
292 return;
293
294 // stop the old current animation
295 if (m_currentAnimation)
296 m_currentAnimation->stop();
297
298 // Assert that the animation passed as argument is actually part of this group ...
299 Q_ASSERT(m_children.contains(anim));
300
301 // ... as then this const_cast is just a shortcut for looking up the non-const
302 // pointer in the linked list of jobs.
303 m_currentAnimation = const_cast<QAbstractAnimationJob *>(anim);
304
305 activateCurrentAnimation(intermediate);
306}
307
308void QSequentialAnimationGroupJob::activateCurrentAnimation(bool intermediate)
309{
310 if (!m_currentAnimation || isStopped())
311 return;
312
313 m_currentAnimation->stop();
314
315 // we ensure the direction is consistent with the group's direction
316 m_currentAnimation->setDirection(m_direction);
317
318 // reset the finish time of the animation if it is uncontrolled
319 if (m_currentAnimation->totalDuration() == -1)
320 resetUncontrolledAnimationFinishTime(m_currentAnimation);
321
322 RETURN_IF_DELETED(m_currentAnimation->start());
323 if (!intermediate && isPaused())
324 m_currentAnimation->pause();
325}
326
327void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
328{
329 Q_UNUSED(animation);
330 Q_ASSERT(animation == m_currentAnimation);
331
332 setUncontrolledAnimationFinishTime(m_currentAnimation, m_currentAnimation->currentTime());
333
334 int totalTime = currentTime();
335 if (m_direction == Forward) {
336 // set the current animation to be the next one
337 if (auto *anim = m_children.next(m_currentAnimation))
338 RETURN_IF_DELETED(setCurrentAnimation(anim));
339
340 for (QAbstractAnimationJob *a = m_children.next(animation); a; a = m_children.next(a)) {
341 int dur = a->duration();
342 if (dur == -1) {
343 totalTime = -1;
344 break;
345 } else {
346 totalTime += dur;
347 }
348 }
349
350 } else {
351 // set the current animation to be the previous one
352 if (auto *anim = m_children.prev(m_currentAnimation))
353 RETURN_IF_DELETED(setCurrentAnimation(anim));
354
355 for (QAbstractAnimationJob *a = m_children.prev(animation); a; a = m_children.prev(a)) {
356 int dur = a->duration();
357 if (dur == -1) {
358 totalTime = -1;
359 break;
360 } else {
361 totalTime += dur;
362 }
363 }
364 }
365 if (totalTime >= 0)
366 setUncontrolledAnimationFinishTime(this, totalTime);
367 if (atEnd())
368 stop();
369}
370
371void QSequentialAnimationGroupJob::animationInserted(QAbstractAnimationJob *anim)
372{
373 if (m_currentAnimation == nullptr)
374 RETURN_IF_DELETED(setCurrentAnimation(m_children.first())); // initialize the current animation
375
376 if (m_currentAnimation == m_children.next(anim)
377 && m_currentAnimation->currentTime() == 0 && m_currentAnimation->currentLoop() == 0) {
378 //in this case we simply insert the animation before the current one has actually started
379 RETURN_IF_DELETED(setCurrentAnimation(anim));
380 }
381
382// TODO
383// if (index < m_currentAnimationIndex || m_currentLoop != 0) {
384// qWarning("QSequentialGroup::insertAnimation only supports to add animations after the current one.");
385// return; //we're not affected because it is added after the current one
386// }
387}
388
389void QSequentialAnimationGroupJob::animationRemoved(QAbstractAnimationJob *anim, QAbstractAnimationJob *prev, QAbstractAnimationJob *next)
390{
391 QAnimationGroupJob::animationRemoved(anim, prev, next);
392
393 Q_ASSERT(m_currentAnimation); // currentAnimation should always be set
394
395 bool removingCurrent = anim == m_currentAnimation;
396 if (removingCurrent) {
397 if (next)
398 RETURN_IF_DELETED(setCurrentAnimation(next)); //let's try to take the next one
399 else if (prev)
400 RETURN_IF_DELETED(setCurrentAnimation(prev));
401 else// case all animations were removed
402 RETURN_IF_DELETED(setCurrentAnimation(nullptr));
403 }
404
405 // duration of the previous animations up to the current animation
406 m_currentTime = 0;
407 for (QAbstractAnimationJob *job : m_children) {
408 if (job == m_currentAnimation)
409 break;
410 m_currentTime = addSaturating(m_currentTime, animationActualTotalDuration(job));
411
412 }
413
414 if (!removingCurrent) {
415 //the current animation is not the one being removed
416 //so we add its current time to the current time of this group
417 m_currentTime = addSaturating(m_currentTime, m_currentAnimation->currentTime());
418 }
419
420 //let's also update the total current time
421 m_totalCurrentTime = addSaturating(m_currentTime, totalDuration());
422}
423
424void QSequentialAnimationGroupJob::debugAnimation(QDebug d) const
425{
426 d << "SequentialAnimationGroupJob(" << Qt::hex << (const void *) this << Qt::dec << ")" << "currentAnimation:" << (void *)m_currentAnimation;
427
428 debugChildren(d);
429}
430
431QT_END_NAMESPACE