qskinny/src/controls/QskHintAnimator.cpp

416 lines
10 KiB
C++
Raw Normal View History

2017-07-21 16:21:34 +00:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskHintAnimator.h"
#include "QskAnimationHint.h"
#include "QskControl.h"
#include "QskEvent.h"
2018-07-19 12:10:48 +00:00
#include <qobject.h>
#include <qthread.h>
2017-07-21 16:21:34 +00:00
2018-08-03 06:15:28 +00:00
#include <algorithm>
2017-07-21 16:21:34 +00:00
#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 )
2017-07-21 16:21:34 +00:00
{
2018-08-03 06:15:28 +00:00
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 >() )
2017-07-21 16:21:34 +00:00
{
auto metrics = value.value< QskBoxShapeMetrics >();
if ( metrics.sizeMode() == Qt::AbsoluteSize )
2017-07-21 16:21:34 +00:00
{
for ( int i = Qt::TopLeftCorner; i <= Qt::BottomRightCorner; i++ )
{
2018-07-05 11:02:25 +00:00
const auto corner = static_cast< Qt::Corner >( i );
metrics.setRadius( corner, qskAligned05( metrics.radius( corner ) ) );
}
2018-07-05 11:02:25 +00:00
return QVariant::fromValue( metrics );
2017-07-21 16:21:34 +00:00
}
}
else if ( value.canConvert< QskMargins >() )
{
const auto margins = value.value< QskMargins >();
return QVariant::fromValue( qskAligned05( margins ) );
}
2017-07-21 16:21:34 +00:00
return value;
}
#endif
2018-07-05 11:02:25 +00:00
static inline bool qskCheckReceiverThread( const QObject* receiver )
{
/*
QskInputPanelSkinlet changes the skin state, what leads to
sending events from the wrong thread. Until we have fixed it
let's block sending the event to avoid running into assertions
in QCoreApplication::sendEvent
*/
2018-07-05 11:02:25 +00:00
const QThread* thread = receiver->thread();
if ( thread == nullptr )
return true;
return ( thread == QThread::currentThread() );
2018-07-05 11:02:25 +00:00
}
QskHintAnimator::QskHintAnimator( const QskAspect aspect, int index ) noexcept
: m_aspect( aspect )
, m_index( index )
2017-07-21 16:21:34 +00:00
{
}
QskHintAnimator::~QskHintAnimator()
{
}
void QskHintAnimator::setAspect( const QskAspect aspect ) noexcept
2017-07-21 16:21:34 +00:00
{
m_aspect = aspect;
}
void QskHintAnimator::setIndex( int index ) noexcept
{
m_index = index;
}
2022-09-04 08:37:40 +00:00
void QskHintAnimator::setUpdateFlags( QskAnimationHint::UpdateFlags flags ) noexcept
{
m_updateFlags = flags;
}
2022-09-04 08:37:40 +00:00
void QskHintAnimator::setControl( QskControl* control ) noexcept
2017-07-21 16:21:34 +00:00
{
m_control = control;
}
void QskHintAnimator::advance( qreal progress )
{
2022-09-04 08:37:40 +00:00
const auto oldValue = currentValue();
2017-07-21 16:21:34 +00:00
Inherited::advance( progress );
#if ALIGN_VALUES
setCurrentValue( qskAligned05( currentValue() ) );
#endif
2017-07-21 16:21:34 +00:00
if ( m_control && ( currentValue() != oldValue ) )
{
if ( m_updateFlags == QskAnimationHint::UpdateAuto )
2017-07-21 16:21:34 +00:00
{
if ( m_aspect.isMetric() )
{
m_control->resetImplicitSize();
if ( !m_control->childItems().isEmpty() )
m_control->polish();
}
m_control->update();
2017-07-21 16:21:34 +00:00
}
else
{
if ( m_updateFlags & QskAnimationHint::UpdateSizeHint )
m_control->resetImplicitSize();
2017-07-21 16:21:34 +00:00
if ( m_updateFlags & QskAnimationHint::UpdatePolish )
m_control->polish();
if ( m_updateFlags & QskAnimationHint::UpdateNode )
m_control->update();
}
2017-07-21 16:21:34 +00:00
}
}
#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
2017-07-21 16:21:34 +00:00
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;
}
};
2017-07-21 16:21:34 +00:00
class AnimatorGuard final : public QObject
{
Q_OBJECT
2018-08-03 06:15:28 +00:00
public:
2017-07-21 16:21:34 +00:00
AnimatorGuard()
{
QskAnimator::addCleanupHandler( this,
2018-07-05 11:02:25 +00:00
SLOT(cleanup()), Qt::QueuedConnection );
2017-07-21 16:21:34 +00:00
}
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 )
2017-07-21 16:21:34 +00:00
m_tables.erase( it );
}
2018-08-03 06:15:28 +00:00
private Q_SLOTS:
2017-07-21 16:21:34 +00:00
void cleanup()
{
2018-08-03 06:15:28 +00:00
for ( auto it = m_tables.begin(); it != m_tables.end(); )
2017-07-21 16:21:34 +00:00
{
if ( ( *it )->cleanup() )
it = m_tables.erase( it );
else
++it;
}
}
2018-08-03 06:15:28 +00:00
private:
2017-07-21 16:21:34 +00:00
// a vector as iteration is more important than insertion
std::vector< QskHintAnimatorTable* > m_tables;
};
Q_GLOBAL_STATIC( AnimatorGuard, qskAnimatorGuard )
}
class QskHintAnimatorTable::PrivateData
{
2018-08-03 06:15:28 +00:00
public:
AnimatorMap animators; // a flat map
2017-07-21 16:21:34 +00:00
};
2018-08-03 06:15:28 +00:00
QskHintAnimatorTable::QskHintAnimatorTable()
2017-07-21 16:21:34 +00:00
{
}
QskHintAnimatorTable::~QskHintAnimatorTable()
{
2021-10-12 15:57:07 +00:00
if ( qskAnimatorGuard )
qskAnimatorGuard->unregisterTable( this );
2022-09-06 05:57:08 +00:00
2021-01-12 15:25:33 +00:00
delete m_data;
2017-07-21 16:21:34 +00:00
}
void QskHintAnimatorTable::start( QskControl* control,
const QskAspect aspect, int index, QskAnimationHint animationHint,
2017-07-21 16:21:34 +00:00
const QVariant& from, const QVariant& to )
{
if ( m_data == nullptr )
{
m_data = new PrivateData();
2022-09-06 05:57:08 +00:00
2021-10-12 15:57:07 +00:00
if ( qskAnimatorGuard )
qskAnimatorGuard->registerTable( this );
2017-07-21 16:21:34 +00:00
}
auto animator = m_data->animators.findOrInsert( aspect, index );
2017-07-21 16:21:34 +00:00
animator->setStartValue( from );
animator->setEndValue( to );
2017-07-21 16:21:34 +00:00
animator->setDuration( animationHint.duration );
animator->setEasingCurve( animationHint.type );
animator->setUpdateFlags( animationHint.updateFlags );
2017-07-21 16:21:34 +00:00
animator->setControl( control );
animator->setWindow( control->window() );
2017-07-21 16:21:34 +00:00
animator->start();
2017-07-21 16:21:34 +00:00
if ( qskCheckReceiverThread( control ) )
{
QskAnimatorEvent event( aspect, index, QskAnimatorEvent::Started );
QCoreApplication::sendEvent( control, &event );
}
2017-07-21 16:21:34 +00:00
}
const QskHintAnimator* QskHintAnimatorTable::animator( QskAspect aspect, int index ) const
2017-07-21 16:21:34 +00:00
{
if ( m_data )
return m_data->animators.find( aspect, index );
2017-07-21 16:21:34 +00:00
return nullptr;
2017-07-21 16:21:34 +00:00
}
QVariant QskHintAnimatorTable::currentValue( QskAspect aspect, int index ) const
2017-07-21 16:21:34 +00:00
{
if ( m_data )
{
if ( auto animator = m_data->animators.find( aspect, index ) )
2017-07-21 16:21:34 +00:00
{
if ( animator->isRunning() )
return animator->currentValue();
2017-07-21 16:21:34 +00:00
}
}
return QVariant();
}
bool QskHintAnimatorTable::cleanup()
{
if ( m_data == nullptr )
return true;
auto& animators = m_data->animators;
for ( auto it = animators.begin(); it != animators.end(); )
2017-07-21 16:21:34 +00:00
{
auto animator = *it;
2017-07-21 16:21:34 +00:00
// remove all terminated animators
if ( !animator->isRunning() )
2017-07-21 16:21:34 +00:00
{
const auto control = animator->control();
const auto aspect = animator->aspect();
const auto index = animator->index();
delete animator;
2017-07-21 16:21:34 +00:00
it = animators.erase( it );
2017-07-21 16:21:34 +00:00
if ( control )
{
if ( qskCheckReceiverThread( control ) )
{
auto event = new QskAnimatorEvent(
aspect, index, QskAnimatorEvent::Terminated );
2020-03-13 17:11:31 +00:00
QCoreApplication::postEvent( control, event );
}
2017-07-21 16:21:34 +00:00
}
}
else
{
++it;
}
}
if ( animators.empty() )
2017-07-21 16:21:34 +00:00
{
delete m_data;
m_data = nullptr;
return true;
}
return false;
}
#include "QskHintAnimator.moc"