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
44QT_BEGIN_NAMESPACE
45
46QSequentialAnimationGroupJob::QSequentialAnimationGroupJob()
47 : QAnimationGroupJob()
48 , m_currentAnimation(nullptr)
49 , m_previousLoop(0)
50{
51}
52
53QSequentialAnimationGroupJob::~QSequentialAnimationGroupJob()
54{
55}
56
57bool 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
73int 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
85QSequentialAnimationGroupJob::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
123void 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
142void 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
167void 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
192int 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
207void 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
216void 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
257void 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
284void 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
291void 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
311void 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
330void 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
374void 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
392void 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
427void 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
434QT_END_NAMESPACE
435

source code of qtdeclarative/src/qml/animations/qsequentialanimationgroupjob.cpp