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