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 <QtCore/qthreadstorage.h> |
41 | |
42 | #include "private/qabstractanimationjob_p.h" |
43 | #include "private/qanimationgroupjob_p.h" |
44 | #include "private/qanimationjobutil_p.h" |
45 | #include "private/qqmlengine_p.h" |
46 | #include "private/qqmlglobal_p.h" |
47 | |
48 | QT_BEGIN_NAMESPACE |
49 | |
50 | #ifndef QT_NO_THREAD |
51 | Q_GLOBAL_STATIC(QThreadStorage<QQmlAnimationTimer *>, animationTimer) |
52 | #endif |
53 | |
54 | DEFINE_BOOL_CONFIG_OPTION(animationTickDump, QML_ANIMATION_TICK_DUMP); |
55 | |
56 | QAnimationJobChangeListener::~QAnimationJobChangeListener() |
57 | { |
58 | } |
59 | |
60 | QQmlAnimationTimer::QQmlAnimationTimer() : |
61 | QAbstractAnimationTimer(), lastTick(0), |
62 | currentAnimationIdx(0), insideTick(false), |
63 | startAnimationPending(false), stopTimerPending(false), |
64 | runningLeafAnimations(0) |
65 | { |
66 | } |
67 | |
68 | void QQmlAnimationTimer::unsetJobTimer(QAbstractAnimationJob *animation) |
69 | { |
70 | if (!animation) |
71 | return; |
72 | if (animation->m_timer == this) |
73 | animation->m_timer = nullptr; |
74 | |
75 | if (animation->isGroup()) { |
76 | QAnimationGroupJob *group = static_cast<QAnimationGroupJob *>(animation); |
77 | for (auto *child = group->firstChild(); child; child = child->nextSibling()) |
78 | unsetJobTimer(animation: child); |
79 | } |
80 | } |
81 | |
82 | QQmlAnimationTimer::~QQmlAnimationTimer() |
83 | { |
84 | for (const auto &animation : qAsConst(t&: animations)) |
85 | unsetJobTimer(animation); |
86 | for (const auto &animation : qAsConst(t&: animationsToStart)) |
87 | unsetJobTimer(animation); |
88 | for (const auto &animation : qAsConst(t&: runningPauseAnimations)) |
89 | unsetJobTimer(animation); |
90 | } |
91 | |
92 | QQmlAnimationTimer *QQmlAnimationTimer::instance(bool create) |
93 | { |
94 | QQmlAnimationTimer *inst; |
95 | if (create && !animationTimer()->hasLocalData()) { |
96 | inst = new QQmlAnimationTimer; |
97 | animationTimer()->setLocalData(inst); |
98 | } else { |
99 | inst = animationTimer() ? animationTimer()->localData() : 0; |
100 | } |
101 | return inst; |
102 | } |
103 | |
104 | QQmlAnimationTimer *QQmlAnimationTimer::instance() |
105 | { |
106 | return instance(create: true); |
107 | } |
108 | |
109 | void QQmlAnimationTimer::ensureTimerUpdate() |
110 | { |
111 | QUnifiedTimer *instU = QUnifiedTimer::instance(create: false); |
112 | if (instU && isPaused) |
113 | instU->updateAnimationTimers(currentTick: -1); |
114 | } |
115 | |
116 | void QQmlAnimationTimer::updateAnimationsTime(qint64 delta) |
117 | { |
118 | //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations |
119 | if (insideTick) |
120 | return; |
121 | |
122 | lastTick += delta; |
123 | |
124 | //we make sure we only call update time if the time has actually changed |
125 | //it might happen in some cases that the time doesn't change because events are delayed |
126 | //when the CPU load is high |
127 | if (delta) { |
128 | insideTick = true; |
129 | for (currentAnimationIdx = 0; currentAnimationIdx < animations.count(); ++currentAnimationIdx) { |
130 | QAbstractAnimationJob *animation = animations.at(i: currentAnimationIdx); |
131 | int elapsed = animation->m_totalCurrentTime |
132 | + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta); |
133 | animation->setCurrentTime(elapsed); |
134 | } |
135 | if (animationTickDump()) { |
136 | qDebug() << "***** Dumping Animation Tree ***** ( tick:" << lastTick << "delta:" << delta << ")" ; |
137 | for (int i = 0; i < animations.count(); ++i) |
138 | qDebug() << animations.at(i); |
139 | } |
140 | insideTick = false; |
141 | currentAnimationIdx = 0; |
142 | } |
143 | } |
144 | |
145 | void QQmlAnimationTimer::updateAnimationTimer() |
146 | { |
147 | restartAnimationTimer(); |
148 | } |
149 | |
150 | void QQmlAnimationTimer::restartAnimationTimer() |
151 | { |
152 | if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty()) |
153 | QUnifiedTimer::pauseAnimationTimer(timer: this, duration: closestPauseAnimationTimeToFinish()); |
154 | else if (isPaused) |
155 | QUnifiedTimer::resumeAnimationTimer(timer: this); |
156 | else if (!isRegistered) |
157 | QUnifiedTimer::startAnimationTimer(timer: this); |
158 | } |
159 | |
160 | void QQmlAnimationTimer::startAnimations() |
161 | { |
162 | if (!startAnimationPending) |
163 | return; |
164 | startAnimationPending = false; |
165 | //force timer to update, which prevents large deltas for our newly added animations |
166 | QUnifiedTimer::instance()->maybeUpdateAnimationsToCurrentTime(); |
167 | |
168 | //we transfer the waiting animations into the "really running" state |
169 | animations += animationsToStart; |
170 | animationsToStart.clear(); |
171 | if (!animations.isEmpty()) |
172 | restartAnimationTimer(); |
173 | } |
174 | |
175 | void QQmlAnimationTimer::stopTimer() |
176 | { |
177 | stopTimerPending = false; |
178 | bool pendingStart = startAnimationPending && animationsToStart.size() > 0; |
179 | if (animations.isEmpty() && !pendingStart) { |
180 | QUnifiedTimer::resumeAnimationTimer(timer: this); |
181 | QUnifiedTimer::stopAnimationTimer(timer: this); |
182 | // invalidate the start reference time |
183 | lastTick = 0; |
184 | } |
185 | } |
186 | |
187 | void QQmlAnimationTimer::registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel) |
188 | { |
189 | if (animation->userControlDisabled()) |
190 | return; |
191 | |
192 | registerRunningAnimation(animation); |
193 | if (isTopLevel) { |
194 | Q_ASSERT(!animation->m_hasRegisteredTimer); |
195 | animation->m_hasRegisteredTimer = true; |
196 | animationsToStart << animation; |
197 | if (!startAnimationPending) { |
198 | startAnimationPending = true; |
199 | QMetaObject::invokeMethod(obj: this, member: "startAnimations" , type: Qt::QueuedConnection); |
200 | } |
201 | } |
202 | } |
203 | |
204 | void QQmlAnimationTimer::unregisterAnimation(QAbstractAnimationJob *animation) |
205 | { |
206 | unregisterRunningAnimation(animation); |
207 | |
208 | if (!animation->m_hasRegisteredTimer) |
209 | return; |
210 | |
211 | int idx = animations.indexOf(t: animation); |
212 | if (idx != -1) { |
213 | animations.removeAt(i: idx); |
214 | // this is needed if we unregister an animation while its running |
215 | if (idx <= currentAnimationIdx) |
216 | --currentAnimationIdx; |
217 | |
218 | if (animations.isEmpty() && !stopTimerPending) { |
219 | stopTimerPending = true; |
220 | QMetaObject::invokeMethod(obj: this, member: "stopTimer" , type: Qt::QueuedConnection); |
221 | } |
222 | } else { |
223 | animationsToStart.removeOne(t: animation); |
224 | } |
225 | animation->m_hasRegisteredTimer = false; |
226 | } |
227 | |
228 | void QQmlAnimationTimer::registerRunningAnimation(QAbstractAnimationJob *animation) |
229 | { |
230 | Q_ASSERT(!animation->userControlDisabled()); |
231 | |
232 | if (animation->m_isGroup) |
233 | return; |
234 | |
235 | if (animation->m_isPause) { |
236 | runningPauseAnimations << animation; |
237 | } else |
238 | runningLeafAnimations++; |
239 | } |
240 | |
241 | void QQmlAnimationTimer::unregisterRunningAnimation(QAbstractAnimationJob *animation) |
242 | { |
243 | unsetJobTimer(animation); |
244 | if (animation->userControlDisabled()) |
245 | return; |
246 | |
247 | if (animation->m_isGroup) |
248 | return; |
249 | |
250 | if (animation->m_isPause) |
251 | runningPauseAnimations.removeOne(t: animation); |
252 | else |
253 | runningLeafAnimations--; |
254 | Q_ASSERT(runningLeafAnimations >= 0); |
255 | } |
256 | |
257 | int QQmlAnimationTimer::closestPauseAnimationTimeToFinish() |
258 | { |
259 | int closestTimeToFinish = INT_MAX; |
260 | for (int i = 0; i < runningPauseAnimations.size(); ++i) { |
261 | QAbstractAnimationJob *animation = runningPauseAnimations.at(i); |
262 | int timeToFinish; |
263 | |
264 | if (animation->direction() == QAbstractAnimationJob::Forward) |
265 | timeToFinish = animation->duration() - animation->currentLoopTime(); |
266 | else |
267 | timeToFinish = animation->currentLoopTime(); |
268 | |
269 | if (timeToFinish < closestTimeToFinish) |
270 | closestTimeToFinish = timeToFinish; |
271 | } |
272 | return closestTimeToFinish; |
273 | } |
274 | |
275 | ///////////////////////////////////////////////////////////////////////////////////////////////////////// |
276 | |
277 | QAbstractAnimationJob::QAbstractAnimationJob() |
278 | : m_loopCount(1) |
279 | , m_group(nullptr) |
280 | , m_direction(QAbstractAnimationJob::Forward) |
281 | , m_state(QAbstractAnimationJob::Stopped) |
282 | , m_totalCurrentTime(0) |
283 | , m_currentTime(0) |
284 | , m_currentLoop(0) |
285 | , m_uncontrolledFinishTime(-1) |
286 | , m_currentLoopStartTime(0) |
287 | , m_nextSibling(nullptr) |
288 | , m_previousSibling(nullptr) |
289 | , m_hasRegisteredTimer(false) |
290 | , m_isPause(false) |
291 | , m_isGroup(false) |
292 | , m_disableUserControl(false) |
293 | , m_hasCurrentTimeChangeListeners(false) |
294 | , m_isRenderThreadJob(false) |
295 | , m_isRenderThreadProxy(false) |
296 | |
297 | { |
298 | } |
299 | |
300 | QAbstractAnimationJob::~QAbstractAnimationJob() |
301 | { |
302 | //we can't call stop here. Otherwise we get pure virtual calls |
303 | if (m_state != Stopped) { |
304 | State oldState = m_state; |
305 | m_state = Stopped; |
306 | stateChanged(newState: oldState, oldState: m_state); |
307 | |
308 | Q_ASSERT(m_state == Stopped); |
309 | if (oldState == Running) { |
310 | if (m_timer) { |
311 | Q_ASSERT(QQmlAnimationTimer::instance(false) == m_timer); |
312 | m_timer->unregisterAnimation(animation: this); |
313 | } |
314 | } |
315 | Q_ASSERT(!m_hasRegisteredTimer); |
316 | } |
317 | |
318 | if (m_group) |
319 | m_group->removeAnimation(animation: this); |
320 | } |
321 | |
322 | void QAbstractAnimationJob::fireTopLevelAnimationLoopChanged() |
323 | { |
324 | m_uncontrolledFinishTime = -1; |
325 | if (m_group) |
326 | m_currentLoopStartTime = 0; |
327 | topLevelAnimationLoopChanged(); |
328 | } |
329 | |
330 | void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState) |
331 | { |
332 | if (m_state == newState) |
333 | return; |
334 | |
335 | if (m_loopCount == 0) |
336 | return; |
337 | |
338 | if (!m_timer) // don't create a timer just to stop the animation |
339 | m_timer = QQmlAnimationTimer::instance(create: newState != Stopped); |
340 | Q_ASSERT(m_timer || newState == Stopped); |
341 | |
342 | State oldState = m_state; |
343 | int oldCurrentTime = m_currentTime; |
344 | int oldCurrentLoop = m_currentLoop; |
345 | Direction oldDirection = m_direction; |
346 | |
347 | // check if we should Rewind |
348 | if ((newState == Paused || newState == Running) && oldState == Stopped) { |
349 | //here we reset the time if needed |
350 | //we don't call setCurrentTime because this might change the way the animation |
351 | //behaves: changing the state or changing the current value |
352 | m_totalCurrentTime = m_currentTime = (m_direction == Forward) ? |
353 | 0 : (m_loopCount == -1 ? duration() : totalDuration()); |
354 | |
355 | // Reset uncontrolled finish time and currentLoopStartTime for this run. |
356 | m_uncontrolledFinishTime = -1; |
357 | if (!m_group) |
358 | m_currentLoopStartTime = m_totalCurrentTime; |
359 | } |
360 | |
361 | m_state = newState; |
362 | //(un)registration of the animation must always happen before calls to |
363 | //virtual function (updateState) to ensure a correct state of the timer |
364 | bool isTopLevel = !m_group || m_group->isStopped(); |
365 | if (oldState == Running) { |
366 | if (newState == Paused && m_hasRegisteredTimer) |
367 | m_timer->ensureTimerUpdate(); |
368 | // the animation is not running any more |
369 | if (m_timer) |
370 | m_timer->unregisterAnimation(animation: this); |
371 | } else if (newState == Running) { |
372 | m_timer->registerAnimation(animation: this, isTopLevel); |
373 | } |
374 | |
375 | //starting an animation qualifies as a top level loop change |
376 | if (newState == Running && oldState == Stopped && !m_group) |
377 | fireTopLevelAnimationLoopChanged(); |
378 | |
379 | RETURN_IF_DELETED(updateState(newState, oldState)); |
380 | |
381 | if (newState != m_state) //this is to be safe if updateState changes the state |
382 | return; |
383 | |
384 | // Notify state change |
385 | RETURN_IF_DELETED(stateChanged(newState, oldState)); |
386 | if (newState != m_state) //this is to be safe if updateState changes the state |
387 | return; |
388 | |
389 | switch (m_state) { |
390 | case Paused: |
391 | break; |
392 | case Running: |
393 | { |
394 | // this ensures that the value is updated now that the animation is running |
395 | if (oldState == Stopped) { |
396 | m_currentLoop = 0; |
397 | if (isTopLevel) { |
398 | // currentTime needs to be updated if pauseTimer is active |
399 | RETURN_IF_DELETED(m_timer->ensureTimerUpdate()); |
400 | RETURN_IF_DELETED(setCurrentTime(m_totalCurrentTime)); |
401 | } |
402 | } |
403 | } |
404 | break; |
405 | case Stopped: |
406 | // Leave running state. |
407 | int dura = duration(); |
408 | |
409 | if (dura == -1 || m_loopCount < 0 |
410 | || (oldDirection == Forward && (oldCurrentTime * (oldCurrentLoop + 1)) == (dura * m_loopCount)) |
411 | || (oldDirection == Backward && oldCurrentTime == 0)) { |
412 | finished(); |
413 | } |
414 | break; |
415 | } |
416 | } |
417 | |
418 | void QAbstractAnimationJob::setDirection(Direction direction) |
419 | { |
420 | if (m_direction == direction) |
421 | return; |
422 | |
423 | if (m_state == Stopped) { |
424 | if (m_direction == Backward) { |
425 | m_currentTime = duration(); |
426 | m_currentLoop = m_loopCount - 1; |
427 | } else { |
428 | m_currentTime = 0; |
429 | m_currentLoop = 0; |
430 | } |
431 | } |
432 | |
433 | // the commands order below is important: first we need to setCurrentTime with the old direction, |
434 | // then update the direction on this and all children and finally restart the pauseTimer if needed |
435 | if (m_hasRegisteredTimer) |
436 | m_timer->ensureTimerUpdate(); |
437 | |
438 | m_direction = direction; |
439 | updateDirection(direction); |
440 | |
441 | if (m_hasRegisteredTimer) |
442 | // needed to update the timer interval in case of a pause animation |
443 | m_timer->updateAnimationTimer(); |
444 | } |
445 | |
446 | void QAbstractAnimationJob::setLoopCount(int loopCount) |
447 | { |
448 | if (m_loopCount == loopCount) |
449 | return; |
450 | m_loopCount = loopCount; |
451 | updateLoopCount(loopCount); |
452 | } |
453 | |
454 | int QAbstractAnimationJob::totalDuration() const |
455 | { |
456 | int dura = duration(); |
457 | if (dura <= 0) |
458 | return dura; |
459 | int loopcount = loopCount(); |
460 | if (loopcount < 0) |
461 | return -1; |
462 | return dura * loopcount; |
463 | } |
464 | |
465 | void QAbstractAnimationJob::setCurrentTime(int msecs) |
466 | { |
467 | msecs = qMax(a: msecs, b: 0); |
468 | // Calculate new time and loop. |
469 | int dura = duration(); |
470 | int totalDura; |
471 | int oldLoop = m_currentLoop; |
472 | |
473 | if (dura < 0 && m_direction == Forward) { |
474 | totalDura = -1; |
475 | if (m_uncontrolledFinishTime >= 0 && msecs >= m_uncontrolledFinishTime) { |
476 | msecs = m_uncontrolledFinishTime; |
477 | if (m_currentLoop == m_loopCount - 1) { |
478 | totalDura = m_uncontrolledFinishTime; |
479 | } else { |
480 | ++m_currentLoop; |
481 | m_currentLoopStartTime = msecs; |
482 | m_uncontrolledFinishTime = -1; |
483 | } |
484 | } |
485 | m_totalCurrentTime = msecs; |
486 | m_currentTime = msecs - m_currentLoopStartTime; |
487 | } else { |
488 | totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount); |
489 | if (totalDura != -1) |
490 | msecs = qMin(a: totalDura, b: msecs); |
491 | m_totalCurrentTime = msecs; |
492 | |
493 | // Update new values. |
494 | m_currentLoop = ((dura <= 0) ? 0 : (msecs / dura)); |
495 | if (m_currentLoop == m_loopCount) { |
496 | //we're at the end |
497 | m_currentTime = qMax(a: 0, b: dura); |
498 | m_currentLoop = qMax(a: 0, b: m_loopCount - 1); |
499 | } else { |
500 | if (m_direction == Forward) { |
501 | m_currentTime = (dura <= 0) ? msecs : (msecs % dura); |
502 | } else { |
503 | m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1; |
504 | if (m_currentTime == dura) |
505 | --m_currentLoop; |
506 | } |
507 | } |
508 | } |
509 | |
510 | |
511 | if (m_currentLoop != oldLoop && !m_group) //### verify Running as well? |
512 | fireTopLevelAnimationLoopChanged(); |
513 | |
514 | RETURN_IF_DELETED(updateCurrentTime(m_currentTime)); |
515 | |
516 | if (m_currentLoop != oldLoop) |
517 | currentLoopChanged(); |
518 | |
519 | // All animations are responsible for stopping the animation when their |
520 | // own end state is reached; in this case the animation is time driven, |
521 | // and has reached the end. |
522 | if ((m_direction == Forward && m_totalCurrentTime == totalDura) |
523 | || (m_direction == Backward && m_totalCurrentTime == 0)) { |
524 | RETURN_IF_DELETED(stop()); |
525 | } |
526 | |
527 | if (m_hasCurrentTimeChangeListeners) |
528 | currentTimeChanged(currentTime: m_currentTime); |
529 | } |
530 | |
531 | void QAbstractAnimationJob::start() |
532 | { |
533 | if (m_state == Running) |
534 | return; |
535 | |
536 | if (QQmlEnginePrivate::designerMode()) { |
537 | if (state() != Stopped) { |
538 | m_currentTime = duration(); |
539 | m_totalCurrentTime = totalDuration(); |
540 | setState(Running); |
541 | setState(Stopped); |
542 | } |
543 | } else { |
544 | setState(Running); |
545 | } |
546 | } |
547 | |
548 | void QAbstractAnimationJob::stop() |
549 | { |
550 | if (m_state == Stopped) |
551 | return; |
552 | setState(Stopped); |
553 | } |
554 | |
555 | void QAbstractAnimationJob::pause() |
556 | { |
557 | if (m_state == Stopped) { |
558 | qWarning(msg: "QAbstractAnimationJob::pause: Cannot pause a stopped animation" ); |
559 | return; |
560 | } |
561 | |
562 | setState(Paused); |
563 | } |
564 | |
565 | void QAbstractAnimationJob::resume() |
566 | { |
567 | if (m_state != Paused) { |
568 | qWarning(msg: "QAbstractAnimationJob::resume: " |
569 | "Cannot resume an animation that is not paused" ); |
570 | return; |
571 | } |
572 | setState(Running); |
573 | } |
574 | |
575 | void QAbstractAnimationJob::setEnableUserControl() |
576 | { |
577 | m_disableUserControl = false; |
578 | } |
579 | |
580 | bool QAbstractAnimationJob::userControlDisabled() const |
581 | { |
582 | return m_disableUserControl; |
583 | } |
584 | |
585 | void QAbstractAnimationJob::setDisableUserControl() |
586 | { |
587 | m_disableUserControl = true; |
588 | start(); |
589 | pause(); |
590 | } |
591 | |
592 | void QAbstractAnimationJob::updateState(QAbstractAnimationJob::State newState, |
593 | QAbstractAnimationJob::State oldState) |
594 | { |
595 | Q_UNUSED(oldState); |
596 | Q_UNUSED(newState); |
597 | } |
598 | |
599 | void QAbstractAnimationJob::updateDirection(QAbstractAnimationJob::Direction direction) |
600 | { |
601 | Q_UNUSED(direction); |
602 | } |
603 | |
604 | void QAbstractAnimationJob::finished() |
605 | { |
606 | //TODO: update this code so it is valid to delete the animation in animationFinished |
607 | for (const auto &change : changeListeners) { |
608 | if (change.types & QAbstractAnimationJob::Completion) { |
609 | RETURN_IF_DELETED(change.listener->animationFinished(this)); |
610 | } |
611 | } |
612 | |
613 | if (m_group && (duration() == -1 || loopCount() < 0)) { |
614 | //this is an uncontrolled animation, need to notify the group animation we are finished |
615 | m_group->uncontrolledAnimationFinished(animation: this); |
616 | } |
617 | } |
618 | |
619 | void QAbstractAnimationJob::stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState) |
620 | { |
621 | for (const auto &change : changeListeners) { |
622 | if (change.types & QAbstractAnimationJob::StateChange) { |
623 | RETURN_IF_DELETED(change.listener->animationStateChanged(this, newState, oldState)); |
624 | } |
625 | } |
626 | } |
627 | |
628 | void QAbstractAnimationJob::currentLoopChanged() |
629 | { |
630 | for (const auto &change : changeListeners) { |
631 | if (change.types & QAbstractAnimationJob::CurrentLoop) { |
632 | RETURN_IF_DELETED(change.listener->animationCurrentLoopChanged(this)); |
633 | } |
634 | } |
635 | } |
636 | |
637 | void QAbstractAnimationJob::currentTimeChanged(int currentTime) |
638 | { |
639 | Q_ASSERT(m_hasCurrentTimeChangeListeners); |
640 | |
641 | for (const auto &change : changeListeners) { |
642 | if (change.types & QAbstractAnimationJob::CurrentTime) { |
643 | RETURN_IF_DELETED(change.listener->animationCurrentTimeChanged(this, currentTime)); |
644 | } |
645 | } |
646 | } |
647 | |
648 | void QAbstractAnimationJob::addAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes) |
649 | { |
650 | if (changes & QAbstractAnimationJob::CurrentTime) |
651 | m_hasCurrentTimeChangeListeners = true; |
652 | |
653 | changeListeners.push_back(x: ChangeListener(listener, changes)); |
654 | } |
655 | |
656 | void QAbstractAnimationJob::removeAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes) |
657 | { |
658 | m_hasCurrentTimeChangeListeners = false; |
659 | |
660 | const auto it = std::find(first: changeListeners.begin(), last: changeListeners.end(), val: ChangeListener(listener, changes)); |
661 | if (it != changeListeners.end()) |
662 | changeListeners.erase(position: it); |
663 | |
664 | for (const auto &change: changeListeners) { |
665 | if (change.types & QAbstractAnimationJob::CurrentTime) { |
666 | m_hasCurrentTimeChangeListeners = true; |
667 | break; |
668 | } |
669 | } |
670 | } |
671 | |
672 | void QAbstractAnimationJob::debugAnimation(QDebug d) const |
673 | { |
674 | d << "AbstractAnimationJob(" << Qt::hex << (const void *) this << Qt::dec << ") state:" |
675 | << m_state << "duration:" << duration(); |
676 | } |
677 | |
678 | QDebug operator<<(QDebug d, const QAbstractAnimationJob *job) |
679 | { |
680 | if (!job) { |
681 | d << "AbstractAnimationJob(null)" ; |
682 | return d; |
683 | } |
684 | job->debugAnimation(d); |
685 | return d; |
686 | } |
687 | |
688 | QT_END_NAMESPACE |
689 | |
690 | //#include "moc_qabstractanimation2_p.cpp" |
691 | #include "moc_qabstractanimationjob_p.cpp" |
692 | |