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