Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
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()) {
45 if (const auto children = group->children()) {
46 for (auto *child : *children)
47 unsetJobTimer(child);
48 }
49 }
50}
51
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
63{
65 if (create && !animationTimer()->hasLocalData()) {
66 inst = new QQmlAnimationTimer;
67 animationTimer()->setLocalData(inst);
68 } else {
69 inst = animationTimer() ? animationTimer()->localData() : 0;
70 }
71 return inst;
72}
73
78
80{
82 if (instU && isPaused)
83 instU->updateAnimationTimers();
84}
85
87{
88 //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations
89 if (insideTick)
90 return;
91
92 lastTick += delta;
93
94 //we make sure we only call update time if the time has actually changed
95 //it might happen in some cases that the time doesn't change because events are delayed
96 //when the CPU load is high
97 if (delta) {
98 insideTick = true;
99 for (currentAnimationIdx = 0; currentAnimationIdx < animations.size(); ++currentAnimationIdx) {
100 QAbstractAnimationJob *animation = animations.at(currentAnimationIdx);
101 int elapsed = animation->m_totalCurrentTime
102 + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta);
104 }
105 if (animationTickDump()) {
106 qDebug() << "***** Dumping Animation Tree ***** ( tick:" << lastTick << "delta:" << delta << ")";
107 for (int i = 0; i < animations.size(); ++i)
108 qDebug() << animations.at(i);
109 }
110 insideTick = false;
111 currentAnimationIdx = 0;
112 }
113}
114
119
121{
122 if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty())
123 QUnifiedTimer::pauseAnimationTimer(this, closestPauseAnimationTimeToFinish());
124 else if (isPaused)
126 else if (!isRegistered)
128}
129
131{
132 if (!startAnimationPending)
133 return;
134 startAnimationPending = false;
135 //force timer to update, which prevents large deltas for our newly added animations
136 QUnifiedTimer::instance()->maybeUpdateAnimationsToCurrentTime();
137
138 //we transfer the waiting animations into the "really running" state
139 animations += animationsToStart;
140 animationsToStart.clear();
141 if (!animations.isEmpty())
143}
144
146{
147 stopTimerPending = false;
148 bool pendingStart = startAnimationPending && animationsToStart.size() > 0;
149 if (animations.isEmpty() && !pendingStart) {
152 // invalidate the start reference time
153 lastTick = 0;
154 }
155}
156
158{
159 if (animation->userControlDisabled())
160 return;
161
162 registerRunningAnimation(animation);
163 if (isTopLevel) {
164 Q_ASSERT(!animation->m_hasRegisteredTimer);
165 animation->m_hasRegisteredTimer = true;
166 animationsToStart << animation;
167 if (!startAnimationPending) {
168 startAnimationPending = true;
169 QMetaObject::invokeMethod(this, "startAnimations", Qt::QueuedConnection);
170 }
171 }
172}
173
175{
176 unregisterRunningAnimation(animation);
177
178 if (!animation->m_hasRegisteredTimer)
179 return;
180
181 int idx = animations.indexOf(animation);
182 if (idx != -1) {
183 animations.removeAt(idx);
184 // this is needed if we unregister an animation while its running
185 if (idx <= currentAnimationIdx)
186 --currentAnimationIdx;
187
188 if (animations.isEmpty() && !stopTimerPending) {
189 stopTimerPending = true;
191 }
192 } else {
193 animationsToStart.removeOne(animation);
194 }
195 animation->m_hasRegisteredTimer = false;
196}
197
198void QQmlAnimationTimer::registerRunningAnimation(QAbstractAnimationJob *animation)
199{
200 Q_ASSERT(!animation->userControlDisabled());
201
202 if (animation->m_isGroup)
203 return;
204
205 if (animation->m_isPause) {
206 runningPauseAnimations << animation;
207 } else
208 runningLeafAnimations++;
209}
210
211void QQmlAnimationTimer::unregisterRunningAnimation(QAbstractAnimationJob *animation)
212{
213 unsetJobTimer(animation);
214 if (animation->userControlDisabled())
215 return;
216
217 if (animation->m_isGroup)
218 return;
219
220 if (!animation->m_isPause)
221 runningLeafAnimations--;
222
223 Q_ASSERT(runningLeafAnimations >= 0);
224}
225
226int QQmlAnimationTimer::closestPauseAnimationTimeToFinish()
227{
228 int closestTimeToFinish = INT_MAX;
229 for (int i = 0; i < runningPauseAnimations.size(); ++i) {
230 QAbstractAnimationJob *animation = runningPauseAnimations.at(i);
231 int timeToFinish;
232
234 timeToFinish = animation->duration() - animation->currentLoopTime();
235 else
236 timeToFinish = animation->currentLoopTime();
237
238 if (timeToFinish < closestTimeToFinish)
239 closestTimeToFinish = timeToFinish;
240 }
241 return closestTimeToFinish;
242}
243
245
247 : m_loopCount(1)
248 , m_group(nullptr)
249 , m_direction(QAbstractAnimationJob::Forward)
250 , m_state(QAbstractAnimationJob::Stopped)
251 , m_totalCurrentTime(0)
252 , m_currentTime(0)
253 , m_currentLoop(0)
254 , m_uncontrolledFinishTime(-1)
255 , m_currentLoopStartTime(0)
256 , m_hasRegisteredTimer(false)
257 , m_isPause(false)
258 , m_isGroup(false)
259 , m_disableUserControl(false)
260 , m_hasCurrentTimeChangeListeners(false)
261 , m_isRenderThreadJob(false)
262 , m_isRenderThreadProxy(false)
263
264{
265}
266
268{
269 //we can't call stop here. Otherwise we get pure virtual calls
270 if (m_state != Stopped) {
271 State oldState = m_state;
273 stateChanged(oldState, m_state);
274
276 if (oldState == Running) {
277 if (m_timer) {
280 }
281 }
283 }
284
285 if (m_group)
287}
288
296
298{
299 if (m_state == newState)
300 return;
301
302 if (m_loopCount == 0)
303 return;
304
305 if (!m_timer) // don't create a timer just to stop the animation
308
309 State oldState = m_state;
310 int oldCurrentTime = m_currentTime;
311 int oldCurrentLoop = m_currentLoop;
312 Direction oldDirection = m_direction;
313
314 // check if we should Rewind
315 if ((newState == Paused || newState == Running) && oldState == Stopped) {
316 //here we reset the time if needed
317 //we don't call setCurrentTime because this might change the way the animation
318 //behaves: changing the state or changing the current value
320 0 : (m_loopCount == -1 ? duration() : totalDuration());
321
322 // Reset uncontrolled finish time and currentLoopStartTime for this run.
324 if (!m_group)
326 }
327
329 //(un)registration of the animation must always happen before calls to
330 //virtual function (updateState) to ensure a correct state of the timer
331 bool isTopLevel = !m_group || m_group->isStopped();
332 if (oldState == Running) {
335 // the animation is not running any more
336 if (m_timer)
338 } else if (newState == Running) {
339 m_timer->registerAnimation(this, isTopLevel);
340 }
341
342 //starting an animation qualifies as a top level loop change
343 if (newState == Running && oldState == Stopped && !m_group)
345
347
348 if (newState != m_state) //this is to be safe if updateState changes the state
349 return;
350
351 // Notify state change
353 if (newState != m_state) //this is to be safe if updateState changes the state
354 return;
355
356 switch (m_state) {
357 case Paused:
358 break;
359 case Running:
360 {
361 // this ensures that the value is updated now that the animation is running
362 if (oldState == Stopped) {
363 m_currentLoop = 0;
364 if (isTopLevel) {
365 // currentTime needs to be updated if pauseTimer is active
368 }
369 }
370 }
371 break;
372 case Stopped:
373 // Leave running state.
374 int dura = duration();
375
376 if (dura == -1 || m_loopCount < 0
377 || (oldDirection == Forward && (oldCurrentTime * (oldCurrentLoop + 1)) == (dura * m_loopCount))
378 || (oldDirection == Backward && oldCurrentTime == 0)) {
379 finished();
380 }
381 break;
382 }
383}
384
386{
387 if (m_direction == direction)
388 return;
389
390 if (m_state == Stopped) {
391 if (m_direction == Backward) {
394 } else {
395 m_currentTime = 0;
396 m_currentLoop = 0;
397 }
398 }
399
400 // the commands order below is important: first we need to setCurrentTime with the old direction,
401 // then update the direction on this and all children and finally restart the pauseTimer if needed
404
407
409 // needed to update the timer interval in case of a pause animation
411}
412
414{
415 if (m_loopCount == loopCount)
416 return;
419}
420
422{
423 int dura = duration();
424 if (dura <= 0)
425 return dura;
426 int loopcount = loopCount();
427 if (loopcount < 0)
428 return -1;
429 return dura * loopcount;
430}
431
433{
434 msecs = qMax(msecs, 0);
435 // Calculate new time and loop.
436 int dura = duration();
437 int totalDura;
438 int oldLoop = m_currentLoop;
439
440 if (dura < 0 && m_direction == Forward) {
441 totalDura = -1;
444 if (m_currentLoop == m_loopCount - 1) {
445 totalDura = m_uncontrolledFinishTime;
446 } else {
450 }
451 }
452 m_totalCurrentTime = msecs;
454 } else {
455 totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount);
456 if (totalDura != -1)
457 msecs = qMin(totalDura, msecs);
458 m_totalCurrentTime = msecs;
459
460 // Update new values.
461 m_currentLoop = ((dura <= 0) ? 0 : (msecs / dura));
462 if (m_currentLoop == m_loopCount) {
463 //we're at the end
464 m_currentTime = qMax(0, dura);
466 } else {
467 if (m_direction == Forward) {
468 m_currentTime = (dura <= 0) ? msecs : (msecs % dura);
469 } else {
470 m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1;
471 if (m_currentTime == dura)
473 }
474 }
475 }
476
477
478 if (m_currentLoop != oldLoop && !m_group) //### verify Running as well?
480
482
483 if (m_currentLoop != oldLoop) {
484 // CurrentLoop listeners may restart the job if e.g. from has changed. Stopping a job will
485 // destroy it, so account for that here.
487 }
488
489 // All animations are responsible for stopping the animation when their
490 // own end state is reached; in this case the animation is time driven,
491 // and has reached the end.
492 if ((m_direction == Forward && m_totalCurrentTime == totalDura)
493 || (m_direction == Backward && m_totalCurrentTime == 0)) {
495 }
496
499}
500
502{
503 if (m_state == Running)
504 return;
505
507 if (state() != Stopped) {
512 }
513 } else {
515 }
516}
517
519{
520 if (m_state == Stopped)
521 return;
523}
524
526{
527 // Simulate the full animation cycle
531}
532
534{
535 if (m_state == Stopped) {
536 qWarning("QAbstractAnimationJob::pause: Cannot pause a stopped animation");
537 return;
538 }
539
541}
542
544{
545 if (m_state != Paused) {
546 qWarning("QAbstractAnimationJob::resume: "
547 "Cannot resume an animation that is not paused");
548 return;
549 }
551}
552
557
562
569
576
581
583{
584 //TODO: update this code so it is valid to delete the animation in animationFinished
585 for (const auto &change : changeListeners) {
586 if (change.types & QAbstractAnimationJob::Completion) {
587 RETURN_IF_DELETED(change.listener->animationFinished(this));
588 }
589 }
590
591 if (m_group && (duration() == -1 || loopCount() < 0)) {
592 //this is an uncontrolled animation, need to notify the group animation we are finished
594 }
595}
596
598{
599 for (const auto &change : changeListeners) {
600 if (change.types & QAbstractAnimationJob::StateChange) {
601 RETURN_IF_DELETED(change.listener->animationStateChanged(this, newState, oldState));
602 }
603 }
604}
605
607{
608 for (const auto &change : changeListeners) {
609 if (change.types & QAbstractAnimationJob::CurrentLoop) {
610 RETURN_IF_DELETED(change.listener->animationCurrentLoopChanged(this));
611 }
612 }
613}
614
616{
618
619 for (const auto &change : changeListeners) {
620 if (change.types & QAbstractAnimationJob::CurrentTime) {
621 RETURN_IF_DELETED(change.listener->animationCurrentTimeChanged(this, currentTime));
622 }
623 }
624}
625
626void QAbstractAnimationJob::addAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
627{
630
631 changeListeners.push_back(ChangeListener(listener, changes));
632}
633
634void QAbstractAnimationJob::removeAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
635{
637
638 const auto it = std::find(changeListeners.begin(), changeListeners.end(), ChangeListener(listener, changes));
639 if (it != changeListeners.end())
640 changeListeners.erase(it);
641
642 for (const auto &change: changeListeners) {
643 if (change.types & QAbstractAnimationJob::CurrentTime) {
645 break;
646 }
647 }
648}
649
651{
652 d << "AbstractAnimationJob(" << Qt::hex << (const void *) this << Qt::dec << ") state:"
653 << m_state << "duration:" << duration();
654}
655
657{
658 if (!job) {
659 d << "AbstractAnimationJob(null)";
660 return d;
661 }
662 job->debugAnimation(d);
663 return d;
664}
665
667
668//#include "moc_qabstractanimation2_p.cpp"
669#include "moc_qabstractanimationjob_p.cpp"
void setDirection(QAbstractAnimationJob::Direction direction)
std::vector< ChangeListener > changeListeners
virtual void debugAnimation(QDebug d) const
virtual void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
QAbstractAnimationJob::State m_state
void addAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes)
virtual void updateDirection(QAbstractAnimationJob::Direction direction)
QAbstractAnimationJob::State state() const
void currentTimeChanged(int currentTime)
void setLoopCount(int loopCount)
QAbstractAnimationJob::Direction m_direction
void stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
virtual void topLevelAnimationLoopChanged()
QAbstractAnimationJob::Direction direction() const
virtual void updateCurrentTime(int)
void setState(QAbstractAnimationJob::State state)
void removeAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes)
Direction direction
the direction of the animation when it is in \l Running state.
int currentLoopTime() const
Returns the current time inside the current loop.
void setCurrentTime(int msecs)
virtual void uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
void removeAnimation(QAbstractAnimationJob *animation)
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
void removeAt(qsizetype i)
Definition qlist.h:590
bool removeOne(const AT &t)
Definition qlist.h:598
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void clear()
Definition qlist.h:434
const QObjectList & children() const
Returns a list of child objects.
Definition qobject.h:201
void unregisterAnimation(QAbstractAnimationJob *animation)
static QQmlAnimationTimer * instance()
void updateAnimationsTime(qint64 timeStep) override
void registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel)
void restartAnimationTimer() override
static bool designerMode()
iterator end()
Definition qset.h:140
\inmodule QtCore
static void stopAnimationTimer(QAbstractAnimationTimer *timer)
static void startAnimationTimer(QAbstractAnimationTimer *timer)
static void resumeAnimationTimer(QAbstractAnimationTimer *timer)
static QUnifiedTimer * instance()
static void pauseAnimationTimer(QAbstractAnimationTimer *timer, int duration)
int duration
the duration of the animation
QSet< QString >::iterator it
direction
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
QTextStream & dec(QTextStream &stream)
Calls QTextStream::setIntegerBase(10) on stream and returns stream.
@ QueuedConnection
QDebug operator<<(QDebug d, const QAbstractAnimationJob *job)
#define RETURN_IF_DELETED(func)
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLboolean GLuint group
#define DEFINE_BOOL_CONFIG_OPTION(name, var)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static double elapsed(qint64 after, qint64 before)
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
static double currentTime()
QObject::connect nullptr
QPropertyAnimation animation
[0]
QLayoutItem * child
[0]
view create()
qsizetype indexOf(const AT &t, qsizetype from=0) const noexcept
Definition qlist.h:962
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...