417 lines
10 KiB
C++
417 lines
10 KiB
C++
/******************************************************************************
|
|
* QSkinny - Copyright (C) The authors
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*****************************************************************************/
|
|
|
|
#include "QskHintAnimator.h"
|
|
#include "QskAnimationHint.h"
|
|
#include "QskControl.h"
|
|
#include "QskEvent.h"
|
|
|
|
#include <qobject.h>
|
|
#include <qthread.h>
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#define ALIGN_VALUES 0
|
|
|
|
#if ALIGN_VALUES
|
|
|
|
static inline qreal qskAligned05( qreal value )
|
|
{
|
|
// aligned to 0.5
|
|
return qRound( 2.0 * value ) / 2.0;
|
|
}
|
|
|
|
static inline QSizeF qskAligned05( const QSizeF& size )
|
|
{
|
|
return QSizeF( qskAligned05( size.width() ), qskAligned05( size.height() ) );
|
|
}
|
|
|
|
static inline QskMargins qskAligned05( const QskMargins& margins )
|
|
{
|
|
const qreal left = qskAligned05( margins.left() );
|
|
const qreal top = qskAligned05( margins.top() );
|
|
const qreal right = qskAligned05( margins.right() );
|
|
const qreal bottom = qskAligned05( margins.bottom() );
|
|
|
|
return QskMargins( left, top, right, bottom );
|
|
}
|
|
|
|
static inline QVariant qskAligned05( const QVariant& value )
|
|
{
|
|
if ( value.canConvert< QskBoxBorderMetrics >() )
|
|
{
|
|
auto metrics = value.value< QskBoxBorderMetrics >();
|
|
|
|
if ( metrics.sizeMode() == Qt::AbsoluteSize )
|
|
{
|
|
metrics.setWidths( qskAligned05( metrics.widths() ) );
|
|
return QVariant::fromValue( metrics );
|
|
}
|
|
}
|
|
else if ( value.canConvert< QskBoxShapeMetrics >() )
|
|
{
|
|
auto metrics = value.value< QskBoxShapeMetrics >();
|
|
if ( metrics.sizeMode() == Qt::AbsoluteSize )
|
|
{
|
|
for ( int i = Qt::TopLeftCorner; i <= Qt::BottomRightCorner; i++ )
|
|
{
|
|
const auto corner = static_cast< Qt::Corner >( i );
|
|
metrics.setRadius( corner, qskAligned05( metrics.radius( corner ) ) );
|
|
}
|
|
|
|
return QVariant::fromValue( metrics );
|
|
}
|
|
}
|
|
else if ( value.canConvert< QskMargins >() )
|
|
{
|
|
const auto margins = value.value< QskMargins >();
|
|
return QVariant::fromValue( qskAligned05( margins ) );
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline void qskSendAnimatorEvent(
|
|
const QskAspect aspect, int index, bool on, QObject* receiver )
|
|
{
|
|
const auto state = on ? QskAnimatorEvent::Started : QskAnimatorEvent::Terminated;
|
|
|
|
const auto thread = receiver->thread();
|
|
if ( thread && ( thread != QThread::currentThread() ) )
|
|
{
|
|
/*
|
|
QskInputPanelSkinlet changes the skin state, what leads to
|
|
sending events from the wrong thread. We can't use
|
|
QCoreApplication::sendEvent then, TODO ...
|
|
*/
|
|
|
|
auto event = new QskAnimatorEvent( aspect, index, state );
|
|
QCoreApplication::postEvent( receiver, event );
|
|
}
|
|
else
|
|
{
|
|
QskAnimatorEvent event( aspect, index, state );
|
|
QCoreApplication::sendEvent( receiver, &event );
|
|
}
|
|
}
|
|
|
|
QskHintAnimator::QskHintAnimator() noexcept
|
|
: m_index( -1 )
|
|
{
|
|
}
|
|
|
|
QskHintAnimator::QskHintAnimator( const QskAspect aspect, int index ) noexcept
|
|
: m_aspect( aspect )
|
|
, m_index( index )
|
|
{
|
|
}
|
|
|
|
QskHintAnimator::~QskHintAnimator()
|
|
{
|
|
}
|
|
|
|
void QskHintAnimator::setAspect( const QskAspect aspect ) noexcept
|
|
{
|
|
m_aspect = aspect;
|
|
}
|
|
|
|
void QskHintAnimator::setIndex( int index ) noexcept
|
|
{
|
|
m_index = index;
|
|
}
|
|
|
|
void QskHintAnimator::setUpdateFlags( QskAnimationHint::UpdateFlags flags ) noexcept
|
|
{
|
|
m_updateFlags = flags;
|
|
}
|
|
|
|
void QskHintAnimator::setControl( QskControl* control ) noexcept
|
|
{
|
|
m_control = control;
|
|
}
|
|
|
|
void QskHintAnimator::advance( qreal progress )
|
|
{
|
|
const auto oldValue = currentValue();
|
|
|
|
Inherited::advance( progress );
|
|
|
|
#if ALIGN_VALUES
|
|
setCurrentValue( qskAligned05( currentValue() ) );
|
|
#endif
|
|
|
|
if ( m_control && ( currentValue() != oldValue ) )
|
|
{
|
|
if ( m_updateFlags == QskAnimationHint::UpdateAuto )
|
|
{
|
|
if ( !m_aspect.isColor() )
|
|
{
|
|
m_control->resetImplicitSize();
|
|
|
|
if ( !m_control->childItems().isEmpty() )
|
|
m_control->polish();
|
|
}
|
|
|
|
m_control->update();
|
|
}
|
|
else
|
|
{
|
|
if ( m_updateFlags & QskAnimationHint::UpdateSizeHint )
|
|
m_control->resetImplicitSize();
|
|
|
|
if ( m_updateFlags & QskAnimationHint::UpdatePolish )
|
|
m_control->polish();
|
|
|
|
if ( m_updateFlags & QskAnimationHint::UpdateNode )
|
|
m_control->update();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
|
|
#include <qdebug.h>
|
|
|
|
QDebug operator<<( QDebug debug, const QskHintAnimator& animator )
|
|
{
|
|
QDebugStateSaver saver( debug );
|
|
debug.nospace();
|
|
|
|
debug << "Animator" << "( ";
|
|
|
|
debug << animator.aspect() << ", " << animator.endValue().typeName() << ", ";
|
|
|
|
if ( animator.index() >= 0 )
|
|
debug << animator.index() << ", ";
|
|
|
|
if ( animator.isRunning() )
|
|
debug << "R: " << animator.duration() << ", " << animator.elapsed();
|
|
else
|
|
debug << "S" << animator.duration();
|
|
|
|
if ( auto control = animator.control() )
|
|
debug << ", " << control->className() << ", " << (void*) control;
|
|
|
|
debug << " )";
|
|
|
|
return debug;
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
class AnimatorMap : public std::vector< QskHintAnimator* >
|
|
{
|
|
public:
|
|
~AnimatorMap()
|
|
{
|
|
qDeleteAll( *this );
|
|
}
|
|
|
|
inline const QskHintAnimator* find( const QskAspect aspect, int index ) const
|
|
{
|
|
const Key key { aspect, index };
|
|
|
|
auto it = std::lower_bound( cbegin(), cend(), key, lessThan );
|
|
if ( it != cend() )
|
|
{
|
|
if ( ( ( *it )->aspect() == aspect ) && ( ( *it )->index() == index ) )
|
|
return *it;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
inline QskHintAnimator* findOrInsert( const QskAspect aspect, int index )
|
|
{
|
|
const Key key { aspect, index };
|
|
|
|
auto it = std::lower_bound( begin(), end(), key, lessThan );
|
|
if ( it == end() || ( *it )->aspect() != aspect || ( *it )->index() != index )
|
|
{
|
|
it = insert( it, new QskHintAnimator( aspect, index ) );
|
|
}
|
|
|
|
return *it;
|
|
}
|
|
|
|
private:
|
|
struct Key
|
|
{
|
|
QskAspect aspect;
|
|
int index;
|
|
};
|
|
|
|
static inline bool lessThan( const QskHintAnimator* animator, const Key& key )
|
|
{
|
|
if ( animator->aspect() == key.aspect )
|
|
return animator->index() < key.index;
|
|
|
|
return animator->aspect() < key.aspect;
|
|
}
|
|
};
|
|
|
|
class AnimatorGuard final : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
AnimatorGuard()
|
|
{
|
|
QskAnimator::addCleanupHandler( this,
|
|
SLOT(cleanup()), Qt::QueuedConnection );
|
|
}
|
|
|
|
void registerTable( QskHintAnimatorTable* table )
|
|
{
|
|
auto it = std::lower_bound( m_tables.begin(), m_tables.end(), table );
|
|
if ( it == m_tables.end() || *it != table )
|
|
m_tables.insert( it, table );
|
|
}
|
|
|
|
void unregisterTable( QskHintAnimatorTable* table )
|
|
{
|
|
auto it = std::lower_bound( m_tables.begin(), m_tables.end(), table );
|
|
if ( it != m_tables.end() && *it == table )
|
|
m_tables.erase( it );
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void cleanup()
|
|
{
|
|
for ( auto it = m_tables.begin(); it != m_tables.end(); )
|
|
{
|
|
if ( ( *it )->cleanup() )
|
|
it = m_tables.erase( it );
|
|
else
|
|
++it;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// a vector as iteration is more important than insertion
|
|
std::vector< QskHintAnimatorTable* > m_tables;
|
|
};
|
|
|
|
Q_GLOBAL_STATIC( AnimatorGuard, qskAnimatorGuard )
|
|
}
|
|
|
|
class QskHintAnimatorTable::PrivateData
|
|
{
|
|
public:
|
|
AnimatorMap animators; // a flat map
|
|
};
|
|
|
|
QskHintAnimatorTable::QskHintAnimatorTable()
|
|
{
|
|
}
|
|
|
|
QskHintAnimatorTable::~QskHintAnimatorTable()
|
|
{
|
|
if ( qskAnimatorGuard )
|
|
qskAnimatorGuard->unregisterTable( this );
|
|
|
|
delete m_data;
|
|
}
|
|
|
|
void QskHintAnimatorTable::start( QskControl* control,
|
|
const QskAspect aspect, int index, QskAnimationHint animationHint,
|
|
const QVariant& from, const QVariant& to )
|
|
{
|
|
if ( m_data == nullptr )
|
|
{
|
|
m_data = new PrivateData();
|
|
|
|
if ( qskAnimatorGuard )
|
|
qskAnimatorGuard->registerTable( this );
|
|
}
|
|
|
|
auto animator = m_data->animators.findOrInsert( aspect, index );
|
|
|
|
animator->setStartValue( from );
|
|
animator->setEndValue( to );
|
|
|
|
animator->setDuration( animationHint.duration );
|
|
animator->setEasingCurve( animationHint.type );
|
|
animator->setUpdateFlags( animationHint.updateFlags );
|
|
|
|
animator->setControl( control );
|
|
animator->setWindow( control->window() );
|
|
|
|
animator->start();
|
|
|
|
qskSendAnimatorEvent( aspect, index, true, control );
|
|
}
|
|
|
|
const QskHintAnimator* QskHintAnimatorTable::animator( QskAspect aspect, int index ) const
|
|
{
|
|
if ( m_data )
|
|
return m_data->animators.find( aspect, index );
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QVariant QskHintAnimatorTable::currentValue( QskAspect aspect, int index ) const
|
|
{
|
|
if ( m_data )
|
|
{
|
|
if ( auto animator = m_data->animators.find( aspect, index ) )
|
|
{
|
|
if ( animator->isRunning() )
|
|
return animator->currentValue();
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool QskHintAnimatorTable::cleanup()
|
|
{
|
|
if ( m_data == nullptr )
|
|
return true;
|
|
|
|
auto& animators = m_data->animators;
|
|
|
|
for ( auto it = animators.begin(); it != animators.end(); )
|
|
{
|
|
auto animator = *it;
|
|
|
|
// remove all terminated animators
|
|
if ( !animator->isRunning() )
|
|
{
|
|
const auto control = animator->control();
|
|
const auto aspect = animator->aspect();
|
|
const auto index = animator->index();
|
|
|
|
delete animator;
|
|
|
|
it = animators.erase( it );
|
|
|
|
if ( control )
|
|
qskSendAnimatorEvent( aspect, index, false, control );
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
if ( animators.empty() )
|
|
{
|
|
delete m_data;
|
|
m_data = nullptr;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#include "QskHintAnimator.moc"
|