/****************************************************************************** * QSkinny - Copyright (C) The authors * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ #include "QskHintAnimator.h" #include "QskAnimationHint.h" #include "QskControl.h" #include "QskEvent.h" #include #include #include #include #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 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"