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
48QT_BEGIN_NAMESPACE
49
50#ifndef QT_NO_THREAD
51Q_GLOBAL_STATIC(QThreadStorage<QQmlAnimationTimer *>, animationTimer)
52#endif
53
54DEFINE_BOOL_CONFIG_OPTION(animationTickDump, QML_ANIMATION_TICK_DUMP);
55
56QAnimationJobChangeListener::~QAnimationJobChangeListener()
57{
58}
59
60QQmlAnimationTimer::QQmlAnimationTimer() :
61 QAbstractAnimationTimer(), lastTick(0),
62 currentAnimationIdx(0), insideTick(false),
63 startAnimationPending(false), stopTimerPending(false),
64 runningLeafAnimations(0)
65{
66}
67
68void 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
82QQmlAnimationTimer::~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
92QQmlAnimationTimer *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
104QQmlAnimationTimer *QQmlAnimationTimer::instance()
105{
106 return instance(create: true);
107}
108
109void QQmlAnimationTimer::ensureTimerUpdate()
110{
111 QUnifiedTimer *instU = QUnifiedTimer::instance(create: false);
112 if (instU && isPaused)
113 instU->updateAnimationTimers(currentTick: -1);
114}
115
116void 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
145void QQmlAnimationTimer::updateAnimationTimer()
146{
147 restartAnimationTimer();
148}
149
150void 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
160void 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
175void 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
187void 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
204void 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
228void 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
241void 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
257int 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
277QAbstractAnimationJob::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
300QAbstractAnimationJob::~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
322void QAbstractAnimationJob::fireTopLevelAnimationLoopChanged()
323{
324 m_uncontrolledFinishTime = -1;
325 if (m_group)
326 m_currentLoopStartTime = 0;
327 topLevelAnimationLoopChanged();
328}
329
330void 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
418void 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
446void QAbstractAnimationJob::setLoopCount(int loopCount)
447{
448 if (m_loopCount == loopCount)
449 return;
450 m_loopCount = loopCount;
451 updateLoopCount(loopCount);
452}
453
454int 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
465void 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
531void 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
548void QAbstractAnimationJob::stop()
549{
550 if (m_state == Stopped)
551 return;
552 setState(Stopped);
553}
554
555void 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
565void 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
575void QAbstractAnimationJob::setEnableUserControl()
576{
577 m_disableUserControl = false;
578}
579
580bool QAbstractAnimationJob::userControlDisabled() const
581{
582 return m_disableUserControl;
583}
584
585void QAbstractAnimationJob::setDisableUserControl()
586{
587 m_disableUserControl = true;
588 start();
589 pause();
590}
591
592void QAbstractAnimationJob::updateState(QAbstractAnimationJob::State newState,
593 QAbstractAnimationJob::State oldState)
594{
595 Q_UNUSED(oldState);
596 Q_UNUSED(newState);
597}
598
599void QAbstractAnimationJob::updateDirection(QAbstractAnimationJob::Direction direction)
600{
601 Q_UNUSED(direction);
602}
603
604void 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
619void 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
628void 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
637void 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
648void 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
656void 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
672void QAbstractAnimationJob::debugAnimation(QDebug d) const
673{
674 d << "AbstractAnimationJob(" << Qt::hex << (const void *) this << Qt::dec << ") state:"
675 << m_state << "duration:" << duration();
676}
677
678QDebug 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
688QT_END_NAMESPACE
689
690//#include "moc_qabstractanimation2_p.cpp"
691#include "moc_qabstractanimationjob_p.cpp"
692

source code of qtdeclarative/src/qml/animations/qabstractanimationjob.cpp