qskinny/src/controls/QskAnimator.cpp

392 lines
8.7 KiB
C++
Raw Normal View History

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"
2018-07-19 14:10:48 +02:00
#include <qelapsedtimer.h>
#include <qglobalstatic.h>
2018-08-03 08:15:28 +02:00
#include <qobject.h>
#include <qquickwindow.h>
#include <qvector.h>
2018-07-19 14:10:48 +02:00
#ifndef QT_NO_DEBUG_STREAM
#include <qdebug.h>
#endif
2017-07-21 18:21:34 +02:00
namespace
{
class Statistics
{
2018-08-03 08:15:28 +02:00
public:
2017-07-21 18:21:34 +02:00
inline Statistics()
{
reset();
}
#ifndef QT_NO_DEBUG_STREAM
2018-08-03 08:15:28 +02:00
void debugStatistics( QDebug debug )
2017-07-21 18:21:34 +02:00
{
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;
};
}
/*
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
2018-08-03 08:15:28 +02:00
public:
2017-07-21 18:21:34 +02:00
QskAnimatorDriver();
void registerAnimator( QskAnimator* );
void unregisterAnimator( QskAnimator* );
qint64 referenceTime() const;
2018-08-03 08:15:28 +02:00
Q_SIGNALS:
2017-07-21 18:21:34 +02:00
void advanced( QQuickWindow* );
void terminated( QQuickWindow* );
2018-08-03 08:15:28 +02:00
private:
2017-07-21 18:21:34 +02:00
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
};
2018-08-03 08:15:28 +02:00
QskAnimatorDriver::QskAnimatorDriver()
: m_index( -1 )
2017-07-21 18:21:34 +02:00
{
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 );
2020-08-07 12:14:47 +02:00
if ( auto window = animator->window() )
2017-07-21 18:21:34 +02:00
{
if ( !m_windows.contains( window ) )
{
m_windows += window;
connect( window, &QQuickWindow::afterAnimating,
2017-12-14 09:41:41 +01:00
this, [ this, window ]() { advanceAnimators( window ); } );
2017-07-21 18:21:34 +02:00
connect( window, &QQuickWindow::frameSwapped,
2017-12-14 09:41:41 +01:00
this, [ this, window ]() { scheduleUpdate( window ); } );
2017-07-21 18:21:34 +02:00
connect( window, &QWindow::visibleChanged,
this, [ this, window ]( bool on ) { if ( !on ) removeWindow( window ); } );
2017-07-21 18:21:34 +02:00
connect( window, &QObject::destroyed,
2017-12-14 09:41:41 +01:00
this, [ this, window ]( QObject* ) { removeWindow( window ); } );
2017-07-21 18:21:34 +02:00
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(); )
{
2018-08-03 08:15:28 +02:00
if ( ( *it )->window() == window )
2017-07-21 18:21:34 +02:00
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
2018-08-03 08:15:28 +02:00
auto animator = m_animators[ m_index ];
2017-07-21 18:21:34 +02:00
if ( animator->window() == window )
{
hasAnimators = true;
if ( animator->isRunning() )
{
animator->update();
if ( !animator->isRunning() )
hasTerminations = true;
}
}
}
2018-08-03 08:15:28 +02:00
m_index = -1;
2017-07-21 18:21:34 +02:00
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 )
2017-10-30 14:38:30 +01:00
Q_GLOBAL_STATIC( Statistics, qskStatistics )
2017-07-21 18:21:34 +02:00
2018-08-03 08:15:28 +02:00
QskAnimator::QskAnimator()
: m_window( nullptr )
, m_duration( 200 )
, m_startTime( -1 )
2017-07-21 18:21:34 +02:00
{
2017-10-30 14:38:30 +01:00
if ( qskStatistics )
qskStatistics->increment();
2017-07-21 18:21:34 +02:00
}
QskAnimator::~QskAnimator()
{
if ( qskAnimatorDriver )
qskAnimatorDriver->unregisterAnimator( this );
2017-10-30 14:38:30 +01:00
if ( qskStatistics )
qskStatistics->decrement();
2017-07-21 18:21:34 +02:00
}
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 )
2018-08-03 08:15:28 +02:00
curveTable[ type ].setType( type );
2017-07-21 18:21:34 +02:00
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;
2020-08-07 12:14:47 +02:00
if ( auto driver = qskAnimatorDriver )
2017-07-21 18:21:34 +02:00
{
driver->registerAnimator( this );
m_startTime = driver->referenceTime();
setup();
}
}
void QskAnimator::stop()
{
if ( !isRunning() )
return;
2020-08-07 12:14:47 +02:00
if ( auto driver = qskAnimatorDriver )
2017-07-21 18:21:34 +02:00
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
}
2018-07-05 13:25:02 +02:00
#if 1
2020-08-07 12:14:47 +02:00
// we should also have functor based callbacks. TODO ...
2018-07-05 13:25:02 +02:00
#endif
2018-08-03 08:15:28 +02:00
QMetaObject::Connection QskAnimator::addCleanupHandler(
QObject* receiver, const char* method, Qt::ConnectionType type )
2017-07-21 18:21:34 +02:00
{
return QObject::connect( qskAnimatorDriver,
2018-07-05 13:25:02 +02:00
SIGNAL(terminated(QQuickWindow*)), receiver, method, type );
2017-07-21 18:21:34 +02:00
}
2018-08-03 08:15:28 +02:00
QMetaObject::Connection QskAnimator::addAdvanceHandler(
QObject* receiver, const char* method, Qt::ConnectionType type )
{
2017-07-21 18:21:34 +02:00
return QObject::connect( qskAnimatorDriver,
2018-07-05 13:25:02 +02:00
SIGNAL(advanced(QQuickWindow*)), receiver, method, type );
2017-07-21 18:21:34 +02:00
}
#ifndef QT_NO_DEBUG_STREAM
2018-08-03 08:15:28 +02:00
void QskAnimator::debugStatistics( QDebug debug )
2017-07-21 18:21:34 +02:00
{
2017-10-30 14:38:30 +01:00
if ( qskStatistics )
qskStatistics->debugStatistics( debug );
2017-07-21 18:21:34 +02:00
}
#endif
#include "QskAnimator.moc"