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