QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskAnimator.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskAnimator.h"
7
8#include <qelapsedtimer.h>
9#include <qglobalstatic.h>
10#include <qobject.h>
11#include <qquickwindow.h>
12#include <qvector.h>
13
14#include <cmath>
15
16#ifndef QT_NO_DEBUG_STREAM
17#include <qdebug.h>
18#endif
19
20namespace
21{
22 class Statistics
23 {
24 public:
25 inline Statistics()
26 {
27 reset();
28 }
29
30#ifndef QT_NO_DEBUG_STREAM
31 void debugStatistics( QDebug debug )
32 {
33 QDebugStateSaver saver( debug );
34 debug.nospace();
35 debug << '(';
36 debug << "created: " << created
37 << ", destroyed: " << destroyed
38 << ", current: " << current
39 << ", maximum: " << maximum;
40 debug << ')';
41 }
42#endif
43
44 inline void reset()
45 {
46 created = destroyed = current = maximum = 0;
47 }
48
49 inline void increment()
50 {
51 created++;
52 current++;
53
54 if ( current > maximum )
55 maximum = current;
56 }
57
58 inline void decrement()
59 {
60 destroyed++;
61 current--;
62 }
63
64 int created;
65 int destroyed;
66 int current;
67 int maximum;
68 };
69}
70
71namespace
72{
73 /*
74 We need to have at least one QObject to connect to QQuickWindow
75 updates - but then we can advance the animators manually without
76 making them heavy QObjects too.
77 */
78 class AnimatorDriver final : public QObject
79 {
80 Q_OBJECT
81
82 public:
83 AnimatorDriver();
84
85 void registerAnimator( QskAnimator* );
86 void unregisterAnimator( QskAnimator* );
87
88 qint64 referenceTime() const;
89
90 Q_SIGNALS:
91 void advanced( QQuickWindow* );
92 void terminated( QQuickWindow* );
93
94 private:
95 void advanceAnimators( QQuickWindow* );
96 void removeWindow( QQuickWindow* );
97 void scheduleUpdate( QQuickWindow* );
98
99 QElapsedTimer m_referenceTime;
100
101 // a sorted vector, good for iterating and good enough for look ups
102 QVector< QskAnimator* > m_animators;
103
104 /*
105 Having a more than a very few windows with running animators is
106 very unlikely and using a hash table instead of a vector probably
107 creates more overhead than being good for something.
108 */
109 QVector< QQuickWindow* > m_windows;
110 mutable int m_index = -1; // current value, when iterating
111 };
112}
113
114AnimatorDriver::AnimatorDriver()
115{
116 m_referenceTime.start();
117}
118
119inline qint64 AnimatorDriver::referenceTime() const
120{
121 return m_referenceTime.elapsed();
122}
123
124void AnimatorDriver::registerAnimator( QskAnimator* animator )
125{
126 Q_ASSERT( animator->window() );
127
128 // do we want to be thread safe ???
129
130 auto it = std::lower_bound( m_animators.begin(), m_animators.end(), animator );
131 if ( it != m_animators.end() && *it == animator )
132 return;
133
134 if ( m_index > 0 )
135 {
136 if ( it - m_animators.begin() < m_index )
137 m_index++;
138 }
139
140 m_animators.insert( it, animator );
141
142 if ( auto window = animator->window() )
143 {
144 if ( !m_windows.contains( window ) )
145 {
146 m_windows += window;
147
148 connect( window, &QQuickWindow::afterAnimating,
149 this, [ this, window ]() { advanceAnimators( window ); } );
150
151 connect( window, &QQuickWindow::frameSwapped,
152 this, [ this, window ]() { scheduleUpdate( window ); } );
153
154 connect( window, &QWindow::visibleChanged,
155 this, [ this, window ]( bool on ) { if ( !on ) removeWindow( window ); } );
156
157 connect( window, &QObject::destroyed,
158 this, [ this, window ]( QObject* ) { removeWindow( window ); } );
159
160 window->update();
161 }
162 }
163}
164
165void AnimatorDriver::scheduleUpdate( QQuickWindow* window )
166{
167 if ( m_windows.contains( window ) )
168 window->update();
169}
170
171void AnimatorDriver::removeWindow( QQuickWindow* window )
172{
173 window->disconnect( this );
174 m_windows.removeOne( window );
175
176 for ( auto it = m_animators.begin(); it != m_animators.end(); )
177 {
178 if ( ( *it )->window() == window )
179 it = m_animators.erase( it );
180 else
181 ++it;
182 }
183}
184
185void AnimatorDriver::unregisterAnimator( QskAnimator* animator )
186{
187 auto it = std::find( m_animators.begin(), m_animators.end(), animator );
188 if ( it != m_animators.end() )
189 {
190 if ( it - m_animators.begin() < m_index )
191 m_index--;
192
193 m_animators.erase( it );
194 }
195}
196
197void AnimatorDriver::advanceAnimators( QQuickWindow* window )
198{
199 bool hasAnimators = false;
200 bool hasTerminations = false;
201
202 for ( m_index = m_animators.size() - 1; m_index >= 0; m_index-- )
203 {
204 // Advancing animators might create/remove animators, what is handled by
205 // adjusting m_index in register/unregister
206
207 auto animator = m_animators[ m_index ];
208 if ( animator->window() == window )
209 {
210 hasAnimators = true;
211
212 if ( animator->isRunning() )
213 {
214 animator->update();
215
216 if ( !animator->isRunning() )
217 hasTerminations = true;
218 }
219 }
220 }
221
222 m_index = -1;
223
224 if ( !hasAnimators )
225 {
226 window->disconnect( this );
227 m_windows.removeOne( const_cast< QQuickWindow* >( window ) );
228 }
229
230 Q_EMIT advanced( window );
231
232 if ( hasTerminations )
233 Q_EMIT terminated( window );
234}
235
236Q_GLOBAL_STATIC( AnimatorDriver, qskAnimatorDriver )
237Q_GLOBAL_STATIC( Statistics, qskStatistics )
238
239QskAnimator::QskAnimator()
240 : m_window( nullptr )
241 , m_duration( 200 )
242 , m_startTime( -1 )
243{
244 if ( qskStatistics )
245 qskStatistics->increment();
246}
247
248QskAnimator::~QskAnimator()
249{
250 if ( qskAnimatorDriver )
251 qskAnimatorDriver->unregisterAnimator( this );
252
253 if ( qskStatistics )
254 qskStatistics->decrement();
255}
256
257QQuickWindow* QskAnimator::window() const
258{
259 return m_window;
260}
261
262void QskAnimator::setWindow( QQuickWindow* window )
263{
264 if ( window != m_window )
265 {
266 stop();
267 m_window = window;
268 }
269}
270
271void QskAnimator::setAutoRepeat( bool on )
272{
273 m_autoRepeat = on;
274}
275
276void QskAnimator::setDuration( int ms )
277{
278 m_duration = ms;
279}
280
281void QskAnimator::setEasingCurve( QEasingCurve::Type type )
282{
283 if ( type >= 0 && type < QEasingCurve::Custom )
284 {
285 // initialize a static curve table once and then reuse
286 // it, instead of recreating a new curve each time.
287
288 static QEasingCurve curveTable[ QEasingCurve::Custom ];
289 if ( curveTable[ type ].type() != type )
290 curveTable[ type ].setType( type );
291
292 m_easingCurve = curveTable[ type ];
293 }
294 else
295 {
296 m_easingCurve.setType( type );
297 }
298}
299
300void QskAnimator::setEasingCurve( const QEasingCurve& easingCurve )
301{
302 m_easingCurve = easingCurve;
303}
304
305const QEasingCurve& QskAnimator::easingCurve() const
306{
307 return m_easingCurve;
308}
309
310qint64 QskAnimator::elapsed() const
311{
312 if ( !isRunning() )
313 return -1;
314
315 const qint64 driverTime = qskAnimatorDriver->referenceTime();
316 return driverTime - m_startTime;
317}
318
319void QskAnimator::start()
320{
321 if ( isRunning() )
322 return;
323
324 if ( auto driver = qskAnimatorDriver )
325 {
326 driver->registerAnimator( this );
327 m_startTime = driver->referenceTime();
328
329 setup();
330 }
331}
332
333void QskAnimator::stop()
334{
335 if ( !isRunning() )
336 return;
337
338 if ( auto driver = qskAnimatorDriver )
339 driver->unregisterAnimator( this );
340
341 m_startTime = -1;
342 done();
343}
344
345void QskAnimator::update()
346{
347 if ( !isRunning() )
348 return;
349
350 const qint64 driverTime = qskAnimatorDriver->referenceTime();
351
352 if ( m_autoRepeat )
353 {
354 double progress = std::fmod( driverTime - m_startTime, m_duration );
355 progress /= m_duration;
356
357 advance( m_easingCurve.valueForProgress( progress ) );
358 }
359 else
360 {
361 double progress = ( driverTime - m_startTime ) / double( m_duration );
362
363 if ( progress > 1.0 )
364 progress = 1.0;
365
366 advance( m_easingCurve.valueForProgress( progress ) );
367
368 if ( progress >= 1.0 )
369 stop();
370 }
371}
372
373void QskAnimator::setup()
374{
375 // nop
376}
377
378void QskAnimator::done()
379{
380 // nop
381}
382
383#if 1
384// we should also have functor based callbacks. TODO ...
385#endif
386
387QMetaObject::Connection QskAnimator::addCleanupHandler(
388 QObject* receiver, const char* method, Qt::ConnectionType type )
389{
390 return QObject::connect( qskAnimatorDriver,
391 SIGNAL(terminated(QQuickWindow*)), receiver, method, type );
392}
393
394QMetaObject::Connection QskAnimator::addAdvanceHandler(
395 QObject* receiver, const char* method, Qt::ConnectionType type )
396{
397 return QObject::connect( qskAnimatorDriver,
398 SIGNAL(advanced(QQuickWindow*)), receiver, method, type );
399}
400
401#ifndef QT_NO_DEBUG_STREAM
402
403void QskAnimator::debugStatistics( QDebug debug )
404{
405 if ( qskStatistics )
406 qskStatistics->debugStatistics( debug );
407}
408
409#endif
410
411#include "QskAnimator.moc"