1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2016 Gunnar Sletta <gunnar@sletta.org>
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtQuick module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "qquickanimatorcontroller_p.h"
42
43#include <private/qquickwindow_p.h>
44#include <private/qsgrenderloop_p.h>
45
46#include <private/qanimationgroupjob_p.h>
47
48#include <QtGui/qscreen.h>
49
50#include <QtCore/qcoreapplication.h>
51
52QT_BEGIN_NAMESPACE
53
54QQuickAnimatorController::~QQuickAnimatorController()
55{
56}
57
58QQuickAnimatorController::QQuickAnimatorController(QQuickWindow *window)
59 : m_window(window)
60{
61}
62
63static void qquickanimator_invalidate_jobs(QAbstractAnimationJob *job)
64{
65 if (job->isRenderThreadJob()) {
66 static_cast<QQuickAnimatorJob *>(job)->invalidate();
67 } else if (job->isGroup()) {
68 QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
69 for (QAbstractAnimationJob *a = g->firstChild(); a; a = a->nextSibling())
70 qquickanimator_invalidate_jobs(a);
71 }
72}
73
74void QQuickAnimatorController::windowNodesDestroyed()
75{
76 for (const QSharedPointer<QAbstractAnimationJob> &toStop : qAsConst(m_rootsPendingStop)) {
77 qquickanimator_invalidate_jobs(toStop.data());
78 toStop->stop();
79 }
80 m_rootsPendingStop.clear();
81
82 // Clear animation roots and iterate over a temporary to avoid that job->stop()
83 // modifies the m_animationRoots and messes with our iteration
84 const auto roots = m_animationRoots;
85 m_animationRoots.clear();
86 for (const QSharedPointer<QAbstractAnimationJob> &job : roots) {
87 qquickanimator_invalidate_jobs(job.data());
88
89 // Stop it and add it to the list of pending start so it might get
90 // started later on.
91 job->stop();
92 m_rootsPendingStart.insert(job);
93 }
94}
95
96void QQuickAnimatorController::advance()
97{
98 bool running = false;
99 for (const QSharedPointer<QAbstractAnimationJob> &job : qAsConst(m_animationRoots)) {
100 if (job->isRunning()) {
101 running = true;
102 break;
103 }
104 }
105
106 for (QQuickAnimatorJob *job : qAsConst(m_runningAnimators))
107 job->commit();
108
109 if (running)
110 m_window->update();
111}
112
113static void qquickanimator_sync_before_start(QAbstractAnimationJob *job)
114{
115 if (job->isRenderThreadJob()) {
116 static_cast<QQuickAnimatorJob *>(job)->preSync();
117 } else if (job->isGroup()) {
118 QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
119 for (QAbstractAnimationJob *a = g->firstChild(); a; a = a->nextSibling())
120 qquickanimator_sync_before_start(a);
121 }
122}
123
124void QQuickAnimatorController::beforeNodeSync()
125{
126 for (const QSharedPointer<QAbstractAnimationJob> &toStop : qAsConst(m_rootsPendingStop)) {
127 toStop->stop();
128 m_animationRoots.remove(toStop.data());
129 }
130 m_rootsPendingStop.clear();
131
132
133 for (QQuickAnimatorJob *job : qAsConst(m_runningAnimators))
134 job->preSync();
135
136 // Start pending jobs
137 for (const QSharedPointer<QAbstractAnimationJob> &job : qAsConst(m_rootsPendingStart)) {
138 Q_ASSERT(!job->isRunning());
139
140 // We want to make sure that presync is called before
141 // updateAnimationTime is called the very first time, so before
142 // starting a tree of jobs, we go through it and call preSync on all
143 // its animators.
144 qquickanimator_sync_before_start(job.data());
145
146 // The start the job..
147 job->start();
148 m_animationRoots.insert(job.data(), job);
149 }
150 m_rootsPendingStart.clear();
151
152 // Issue an update directly on the window to force another render pass.
153 if (m_animationRoots.size())
154 m_window->update();
155}
156
157void QQuickAnimatorController::afterNodeSync()
158{
159 for (QQuickAnimatorJob *job : qAsConst(m_runningAnimators))
160 job->postSync();
161}
162
163void QQuickAnimatorController::animationFinished(QAbstractAnimationJob *job)
164{
165 m_animationRoots.remove(job);
166}
167
168void QQuickAnimatorController::animationStateChanged(QAbstractAnimationJob *job,
169 QAbstractAnimationJob::State newState,
170 QAbstractAnimationJob::State oldState)
171{
172 Q_ASSERT(job->isRenderThreadJob());
173 QQuickAnimatorJob *animator = static_cast<QQuickAnimatorJob *>(job);
174 if (newState == QAbstractAnimationJob::Running) {
175 m_runningAnimators.insert(animator);
176 } else if (oldState == QAbstractAnimationJob::Running) {
177 animator->commit();
178 m_runningAnimators.remove(animator);
179 }
180}
181
182
183void QQuickAnimatorController::requestSync()
184{
185 // Force a "sync" pass as the newly started animation needs to sync properties from GUI.
186 m_window->maybeUpdate();
187}
188
189// All this is being executed on the GUI thread while the animator controller
190// is locked.
191void QQuickAnimatorController::start_helper(QAbstractAnimationJob *job)
192{
193 if (job->isRenderThreadJob()) {
194 QQuickAnimatorJob *j = static_cast<QQuickAnimatorJob *>(job);
195 j->addAnimationChangeListener(this, QAbstractAnimationJob::StateChange);
196 j->initialize(this);
197 } else if (job->isGroup()) {
198 QAnimationGroupJob *g = static_cast<QAnimationGroupJob *>(job);
199 for (QAbstractAnimationJob *a = g->firstChild(); a; a = a->nextSibling())
200 start_helper(a);
201 }
202}
203
204// Called by the proxy when it is time to kick off an animation job
205void QQuickAnimatorController::start(const QSharedPointer<QAbstractAnimationJob> &job)
206{
207 m_rootsPendingStart.insert(job);
208 m_rootsPendingStop.remove(job);
209 job->addAnimationChangeListener(this, QAbstractAnimationJob::Completion);
210 start_helper(job.data());
211 requestSync();
212}
213
214
215// Called by the proxy when it is time to stop an animation job.
216void QQuickAnimatorController::cancel(const QSharedPointer<QAbstractAnimationJob> &job)
217{
218 m_rootsPendingStart.remove(job);
219 m_rootsPendingStop.insert(job);
220}
221
222
223QT_END_NAMESPACE
224
225#include "moc_qquickanimatorcontroller_p.cpp"
226