Qt 6.x
The Qt SDK
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
17#endif
18
19DEFINE_BOOL_CONFIG_OPTION(animationTickDump, QML_ANIMATION_TICK_DUMP);
20
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->isGroup()) {
42 if (const auto children = group->children()) {
43 for (auto *child : *children)
44 unsetJobTimer(child);
45 }
46 }
47}
48
50{
51 for (const auto &animation : std::as_const(animations))
52 unsetJobTimer(animation);
53 for (const auto &animation : std::as_const(animationsToStart))
54 unsetJobTimer(animation);
55 for (const auto &animation : std::as_const(runningPauseAnimations))
56 unsetJobTimer(animation);
57}
58
60{
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
72{
73 return instance(true);
74}
75
77{
79 if (instU && isPaused)
80 instU->updateAnimationTimers();
81}
82
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(currentAnimationIdx);
98 int elapsed = animation->m_totalCurrentTime
99 + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta);
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
113{
115}
116
118{
119 if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty())
120 QUnifiedTimer::pauseAnimationTimer(this, closestPauseAnimationTimeToFinish());
121 else if (isPaused)
123 else if (!isRegistered)
125}
126
128{
129 if (!startAnimationPending)
130 return;
131 startAnimationPending = false;
132 //force timer to update, which prevents large deltas for our newly added animations
134
135 //we transfer the waiting animations into the "really running" state
136 animations += animationsToStart;
137 animationsToStart.clear();
138 if (!animations.isEmpty())
140}
141
143{
144 stopTimerPending = false;
145 bool pendingStart = startAnimationPending && animationsToStart.size() > 0;
146 if (animations.isEmpty() && !pendingStart) {
149 // invalidate the start reference time
150 lastTick = 0;
151 }
153
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(this, "startAnimations", Qt::QueuedConnection);
167 }
168 }
169}
170
172{
173 unregisterRunningAnimation(animation);
174
175 if (!animation->m_hasRegisteredTimer)
176 return;
177
178 int idx = animations.indexOf(animation);
179 if (idx != -1) {
180 animations.removeAt(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;
188 }
189 } else {
190 animationsToStart.removeOne(animation);
191 }
192 animation->m_hasRegisteredTimer = false;
193}
194
195void 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
208void 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(animation);
219 else
220 runningLeafAnimations--;
221 Q_ASSERT(runningLeafAnimations >= 0);
222}
223
224int 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
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
243
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
266{
267 //we can't call stop here. Otherwise we get pure virtual calls
268 if (m_state != Stopped) {
269 State oldState = m_state;
271 stateChanged(oldState, m_state);
272
274 if (oldState == Running) {
275 if (m_timer) {
278 }
279 }
281 }
282
283 if (m_group)
285}
286
288{
290 if (m_group)
293}
294
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
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
318 0 : (m_loopCount == -1 ? duration() : totalDuration());
319
320 // Reset uncontrolled finish time and currentLoopStartTime for this run.
322 if (!m_group)
324 }
325
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) {
333 // the animation is not running any more
334 if (m_timer)
336 } else if (newState == Running) {
337 m_timer->registerAnimation(this, isTopLevel);
338 }
339
340 //starting an animation qualifies as a top level loop change
341 if (newState == Running && oldState == Stopped && !m_group)
343
345
346 if (newState != m_state) //this is to be safe if updateState changes the state
347 return;
348
349 // Notify state change
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
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
384{
385 if (m_direction == direction)
386 return;
387
388 if (m_state == Stopped) {
389 if (m_direction == Backward) {
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
402
405
407 // needed to update the timer interval in case of a pause animation
409}
410
412{
413 if (m_loopCount == loopCount)
414 return;
417}
418
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
431{
432 msecs = qMax(msecs, 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;
442 if (m_currentLoop == m_loopCount - 1) {
443 totalDura = m_uncontrolledFinishTime;
444 } else {
448 }
449 }
450 m_totalCurrentTime = msecs;
452 } else {
453 totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount);
454 if (totalDura != -1)
455 msecs = qMin(totalDura, 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(0, dura);
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)
471 }
472 }
473 }
474
475
476 if (m_currentLoop != oldLoop && !m_group) //### verify Running as well?
478
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.
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)) {
493 }
494
497}
498
500{
501 if (m_state == Running)
502 return;
503
505 if (state() != Stopped) {
510 }
511 } else {
513 }
514}
515
517{
518 if (m_state == Stopped)
519 return;
521}
522
524{
525 // Simulate the full animation cycle
529}
530
532{
533 if (m_state == Stopped) {
534 qWarning("QAbstractAnimationJob::pause: Cannot pause a stopped animation");
535 return;
536 }
537
539}
540
542{
543 if (m_state != Paused) {
544 qWarning("QAbstractAnimationJob::resume: "
545 "Cannot resume an animation that is not paused");
546 return;
547 }
549}
550
552{
553 m_disableUserControl = false;
554}
555
557{
559}
560
562{
564 start();
565 pause();
566}
567
570{
571 Q_UNUSED(oldState);
573}
574
576{
578}
579
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
592 }
593}
594
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
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
614{
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
624void QAbstractAnimationJob::addAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
625{
628
629 changeListeners.push_back(ChangeListener(listener, changes));
630}
631
632void QAbstractAnimationJob::removeAnimationChangeListener(QAnimationJobChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
633{
635
636 const auto it = std::find(changeListeners.begin(), changeListeners.end(), ChangeListener(listener, changes));
637 if (it != changeListeners.end())
638 changeListeners.erase(it);
639
640 for (const auto &change: changeListeners) {
641 if (change.types & QAbstractAnimationJob::CurrentTime) {
643 break;
644 }
645 }
646}
647
649{
650 d << "AbstractAnimationJob(" << Qt::hex << (const void *) this << Qt::dec << ") state:"
651 << m_state << "duration:" << duration();
652}
653
655{
656 if (!job) {
657 d << "AbstractAnimationJob(null)";
658 return d;
659 }
660 job->debugAnimation(d);
661 return d;
662}
663
665
666//#include "moc_qabstractanimation2_p.cpp"
667#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:386
bool isEmpty() const noexcept
Definition qlist.h:390
void removeAt(qsizetype i)
Definition qlist.h:573
bool removeOne(const AT &t)
Definition qlist.h:581
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void clear()
Definition qlist.h:417
const QObjectList & children() const
Returns a list of child objects.
Definition qobject.h:171
void unregisterAnimation(QAbstractAnimationJob *animation)
static QQmlAnimationTimer * instance()
void updateAnimationsTime(qint64 timeStep) override
void registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel)
void restartAnimationTimer() override
static bool designerMode()
\inmodule QtCore
\inmodule QtCore
static void stopAnimationTimer(QAbstractAnimationTimer *timer)
static void startAnimationTimer(QAbstractAnimationTimer *timer)
void maybeUpdateAnimationsToCurrentTime()
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:160
#define qWarning
Definition qlogging.h:162
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:55
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:955
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...