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