2017-07-21 18:21:34 +02:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
#include "QskAnimator.h"
|
|
|
|
|
|
|
|
#include <QObject>
|
|
|
|
#include <QVector>
|
|
|
|
#include <QQuickWindow>
|
|
|
|
#include <QElapsedTimer>
|
|
|
|
#include <QGlobalStatic>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
class Statistics
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
inline Statistics()
|
|
|
|
{
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
|
|
void debugStatistics( QDebug debug )
|
|
|
|
{
|
|
|
|
QDebugStateSaver saver( debug );
|
|
|
|
debug.nospace();
|
|
|
|
debug << '(';
|
|
|
|
debug << "created: " << created
|
|
|
|
<< ", destroyed: " << destroyed
|
|
|
|
<< ", current: " << current
|
|
|
|
<< ", maximum: " << maximum;
|
|
|
|
debug << ')';
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
inline void reset()
|
|
|
|
{
|
|
|
|
created = destroyed = current = maximum = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void increment()
|
|
|
|
{
|
|
|
|
created++;
|
|
|
|
current++;
|
|
|
|
|
|
|
|
if ( current > maximum )
|
|
|
|
maximum = current;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void decrement()
|
|
|
|
{
|
|
|
|
destroyed++;
|
|
|
|
current--;
|
|
|
|
}
|
|
|
|
|
|
|
|
int created;
|
|
|
|
int destroyed;
|
|
|
|
int current;
|
|
|
|
int maximum;
|
|
|
|
};
|
|
|
|
|
|
|
|
static Statistics qskStatistics;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
We need to have at least one QObject to connect to QQuickWindow
|
|
|
|
updates - but then we can advance the animators manually without
|
|
|
|
making them heavy QObjects too.
|
|
|
|
*/
|
|
|
|
class QskAnimatorDriver final : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
QskAnimatorDriver();
|
|
|
|
|
|
|
|
void registerAnimator( QskAnimator* );
|
|
|
|
void unregisterAnimator( QskAnimator* );
|
|
|
|
|
|
|
|
qint64 referenceTime() const;
|
|
|
|
|
|
|
|
Q_SIGNALS:
|
|
|
|
void advanced( QQuickWindow* );
|
|
|
|
void terminated( QQuickWindow* );
|
|
|
|
|
|
|
|
private:
|
|
|
|
void advanceAnimators( QQuickWindow* );
|
|
|
|
void removeWindow( QQuickWindow* );
|
|
|
|
void scheduleUpdate( QQuickWindow* );
|
|
|
|
|
|
|
|
QElapsedTimer m_referenceTime;
|
|
|
|
|
|
|
|
// a sorted vector, good for iterating and good enough for look ups
|
|
|
|
QVector< QskAnimator* > m_animators;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Having a more than a very few windows with running animators is
|
|
|
|
very unlikely and using a hash table instead of a vector probably
|
|
|
|
creates more overhead than being good for something.
|
|
|
|
*/
|
|
|
|
QVector< QQuickWindow* > m_windows;
|
|
|
|
mutable int m_index; // current value, when iterating
|
|
|
|
};
|
|
|
|
|
|
|
|
QskAnimatorDriver::QskAnimatorDriver():
|
|
|
|
m_index( -1 )
|
|
|
|
{
|
|
|
|
m_referenceTime.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline qint64 QskAnimatorDriver::referenceTime() const
|
|
|
|
{
|
|
|
|
return m_referenceTime.elapsed();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimatorDriver::registerAnimator( QskAnimator* animator )
|
|
|
|
{
|
|
|
|
Q_ASSERT( animator->window() );
|
|
|
|
|
|
|
|
// do we want to be thread safe ???
|
|
|
|
|
|
|
|
auto it = std::lower_bound( m_animators.begin(), m_animators.end(), animator );
|
|
|
|
if ( it != m_animators.end() && *it == animator )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( m_index > 0 )
|
|
|
|
{
|
|
|
|
if ( it - m_animators.begin() < m_index )
|
|
|
|
m_index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_animators.insert( it, animator );
|
|
|
|
|
|
|
|
QQuickWindow* window = animator->window();
|
|
|
|
if ( window )
|
|
|
|
{
|
|
|
|
if ( !m_windows.contains( window ) )
|
|
|
|
{
|
|
|
|
m_windows += window;
|
|
|
|
|
|
|
|
connect( window, &QQuickWindow::afterAnimating,
|
|
|
|
this, [ = ]() { advanceAnimators( window ); } );
|
|
|
|
|
|
|
|
connect( window, &QQuickWindow::frameSwapped,
|
|
|
|
this, [ = ]() { scheduleUpdate( window ); } );
|
|
|
|
|
|
|
|
connect( window, &QObject::destroyed,
|
|
|
|
this, [ = ]( QObject* ) { removeWindow( window ); } );
|
|
|
|
|
|
|
|
window->update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimatorDriver::scheduleUpdate( QQuickWindow* window )
|
|
|
|
{
|
|
|
|
if ( m_windows.contains( window ) )
|
|
|
|
window->update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimatorDriver::removeWindow( QQuickWindow* window )
|
|
|
|
{
|
|
|
|
window->disconnect( this );
|
|
|
|
m_windows.removeOne( window );
|
|
|
|
|
|
|
|
for ( auto it = m_animators.begin(); it != m_animators.end(); )
|
|
|
|
{
|
|
|
|
if ( (*it)->window() == window )
|
|
|
|
it = m_animators.erase( it );
|
|
|
|
else
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimatorDriver::unregisterAnimator( QskAnimator* animator )
|
|
|
|
{
|
|
|
|
auto it = std::find( m_animators.begin(), m_animators.end(), animator );
|
|
|
|
if ( it != m_animators.end() )
|
|
|
|
{
|
|
|
|
if ( it - m_animators.begin() < m_index )
|
|
|
|
m_index--;
|
|
|
|
|
|
|
|
m_animators.erase( it );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimatorDriver::advanceAnimators( QQuickWindow* window )
|
|
|
|
{
|
|
|
|
bool hasAnimators = false;
|
|
|
|
bool hasTerminations = false;
|
|
|
|
|
|
|
|
for ( m_index = m_animators.size() - 1; m_index >= 0; m_index-- )
|
|
|
|
{
|
|
|
|
// Advancing animators might create/remove animators, what is handled by
|
|
|
|
// adjusting m_index in register/unregister
|
|
|
|
|
|
|
|
QskAnimator* animator = m_animators[m_index];
|
|
|
|
if ( animator->window() == window )
|
|
|
|
{
|
|
|
|
hasAnimators = true;
|
|
|
|
|
|
|
|
if ( animator->isRunning() )
|
|
|
|
{
|
|
|
|
animator->update();
|
|
|
|
|
|
|
|
if ( !animator->isRunning() )
|
|
|
|
hasTerminations = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_index = -1;
|
|
|
|
|
|
|
|
if ( !hasAnimators )
|
|
|
|
{
|
|
|
|
window->disconnect( this );
|
|
|
|
m_windows.removeOne( const_cast< QQuickWindow* >( window ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT advanced( window );
|
|
|
|
|
|
|
|
if ( hasTerminations )
|
|
|
|
Q_EMIT terminated( window );
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_GLOBAL_STATIC( QskAnimatorDriver, qskAnimatorDriver )
|
|
|
|
|
|
|
|
QskAnimator::QskAnimator():
|
|
|
|
m_window( nullptr ),
|
|
|
|
m_duration( 200 ),
|
|
|
|
m_startTime( -1 )
|
|
|
|
{
|
|
|
|
qskStatistics.increment();
|
|
|
|
}
|
|
|
|
|
|
|
|
QskAnimator::~QskAnimator()
|
|
|
|
{
|
|
|
|
if ( qskAnimatorDriver )
|
|
|
|
qskAnimatorDriver->unregisterAnimator( this );
|
|
|
|
|
|
|
|
qskStatistics.decrement();
|
|
|
|
}
|
|
|
|
|
|
|
|
QQuickWindow* QskAnimator::window() const
|
|
|
|
{
|
|
|
|
return m_window;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::setWindow( QQuickWindow* window )
|
|
|
|
{
|
|
|
|
if ( window != m_window )
|
|
|
|
{
|
|
|
|
stop();
|
|
|
|
m_window = window;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::setDuration( int ms )
|
|
|
|
{
|
|
|
|
m_duration = ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::setEasingCurve( QEasingCurve::Type type )
|
|
|
|
{
|
|
|
|
if ( type >= 0 && type < QEasingCurve::Custom )
|
|
|
|
{
|
|
|
|
// initialize a static curve table once and then reuse
|
|
|
|
// it, instead of recreating a new curve each time.
|
|
|
|
|
|
|
|
static QEasingCurve curveTable[ QEasingCurve::Custom ];
|
|
|
|
if ( curveTable[ type ].type() != type )
|
|
|
|
curveTable[ type ].setType( type );
|
|
|
|
|
|
|
|
m_easingCurve = curveTable[ type ];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_easingCurve.setType( type );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::setEasingCurve( const QEasingCurve& easingCurve )
|
|
|
|
{
|
|
|
|
m_easingCurve = easingCurve;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QEasingCurve& QskAnimator::easingCurve() const
|
|
|
|
{
|
|
|
|
return m_easingCurve;
|
|
|
|
}
|
|
|
|
|
|
|
|
int QskAnimator::elapsed() const
|
|
|
|
{
|
|
|
|
if ( !isRunning() )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
const qint64 driverTime = qskAnimatorDriver->referenceTime();
|
|
|
|
return driverTime - m_startTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::start()
|
|
|
|
{
|
|
|
|
if ( isRunning() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( QskAnimatorDriver* driver = qskAnimatorDriver )
|
|
|
|
{
|
|
|
|
driver->registerAnimator( this );
|
|
|
|
m_startTime = driver->referenceTime();
|
|
|
|
|
|
|
|
setup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::stop()
|
|
|
|
{
|
|
|
|
if ( !isRunning() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( QskAnimatorDriver* driver = qskAnimatorDriver )
|
|
|
|
driver->unregisterAnimator( this );
|
|
|
|
|
|
|
|
m_startTime = -1;
|
|
|
|
done();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::update()
|
|
|
|
{
|
|
|
|
if ( !isRunning() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const qint64 driverTime = qskAnimatorDriver->referenceTime();
|
|
|
|
|
|
|
|
double progress = ( driverTime - m_startTime ) / double( m_duration );
|
|
|
|
if ( progress > 1.0 )
|
|
|
|
progress = 1.0;
|
|
|
|
|
|
|
|
advance( m_easingCurve.valueForProgress( progress ) );
|
|
|
|
|
|
|
|
if ( progress >= 1.0 )
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::setup()
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskAnimator::done()
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
}
|
|
|
|
|
|
|
|
QMetaObject::Connection QskAnimator::addCleanupHandler( QObject* receiver,
|
|
|
|
const char* method, Qt::ConnectionType type )
|
|
|
|
{
|
|
|
|
return QObject::connect( qskAnimatorDriver,
|
2017-10-30 08:48:49 +01:00
|
|
|
SIGNAL( terminated(QQuickWindow*) ), receiver, method, type );
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QMetaObject::Connection QskAnimator::addAdvanceHandler( QObject* receiver,
|
|
|
|
const char* method, Qt::ConnectionType type )
|
|
|
|
{
|
|
|
|
return QObject::connect( qskAnimatorDriver,
|
2017-10-30 08:48:49 +01:00
|
|
|
SIGNAL( advanced(QQuickWindow*) ), receiver, method, type );
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
|
|
|
|
|
|
void QskAnimator::debugStatistics( QDebug debug )
|
|
|
|
{
|
|
|
|
qskStatistics.debugStatistics( debug );
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "QskAnimator.moc"
|