qskinny/src/controls/QskSkinTransition.cpp

570 lines
17 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
#include "QskSkinTransition.h"
#include "QskControl.h"
#include "QskSkin.h"
#include "QskSkinHintTable.h"
2017-07-21 18:21:34 +02:00
#include "QskColorFilter.h"
#include "QskHintAnimator.h"
#include <QGuiApplication>
#include <QQuickWindow>
#include <QVector>
#include <QObject>
#include <QPointer>
#include <QGlobalStatic>
#include <unordered_map>
#include <vector>
namespace
{
class UpdateInfo
{
public:
enum UpdateMode
{
Polish = 1,
Update = 2
};
static inline bool compare( const UpdateInfo& i1, const UpdateInfo& i2 )
{
return i1.control.data() < i2.control.data();
}
QPointer< QskControl > control;
int updateModes;
};
class AnimatorCandidate
{
public:
AnimatorCandidate() = default;
inline AnimatorCandidate( QskAspect::Aspect aspect,
QVariant from, QVariant to ):
aspect( aspect ),
from( from ),
to( to )
{
}
QskAspect::Aspect aspect;
QVariant from;
QVariant to;
};
}
static QVector< AnimatorCandidate > qskAnimatorCandidates(
QskSkinTransition::Type mask,
const QskSkinHintTable& oldTable,
2017-07-21 18:21:34 +02:00
const std::unordered_map< int, QskColorFilter >& oldFilters,
const QskSkinHintTable& newTable,
2017-07-21 18:21:34 +02:00
const std::unordered_map< int, QskColorFilter >& newFilters )
{
// building a list of candidates for animations by comparing
// the old/new set of skin hints
const QskColorFilter noFilter;
2017-07-21 18:21:34 +02:00
QVector< AnimatorCandidate > candidates;
if ( !oldTable.hasHints() )
2017-07-21 18:21:34 +02:00
return candidates;
2017-10-30 08:33:43 +01:00
for ( const auto& entry : newTable.hints() )
2017-07-21 18:21:34 +02:00
{
const auto aspect = entry.first;
if ( aspect.isAnimator() )
continue;
const auto type = aspect.type();
if ( type == QskAspect::Flag )
{
switch( aspect.flagPrimitive() )
{
case QskAspect::GraphicRole:
{
if ( !( mask & QskSkinTransition::Color ) )
continue;
int role1 = 0;
2017-07-21 18:21:34 +02:00
const auto value = oldTable.resolvedHint( aspect );
if ( value )
role1 = value->toInt();
const int role2 = entry.second.toInt();
/*
When the role is the same we already have the animators
for the graphic filter table running
*/
if ( role1 != role2 )
2017-07-21 18:21:34 +02:00
{
const auto it1 = oldFilters.find( role1 );
const auto it2 = newFilters.find( role2 );
2017-07-21 18:21:34 +02:00
if ( it1 != oldFilters.end() || it2 != newFilters.end() )
2017-07-21 18:21:34 +02:00
{
const auto& f1 = ( it1 != oldFilters.end() ) ? it1->second : noFilter;
const auto& f2 = ( it2 != newFilters.end() ) ? it2->second : noFilter;
if ( f1 != f2 )
{
candidates += AnimatorCandidate( aspect,
QVariant::fromValue( f1 ),
QVariant::fromValue( f2 ) );
}
2017-07-21 18:21:34 +02:00
}
}
break;
}
case QskAspect::FontRole:
{
if ( !( mask & QskSkinTransition::Metric ) )
continue;
break;
}
default:
;
}
}
else
{
if ( ( ( type == QskAspect::Color ) && ( mask & QskSkinTransition::Color ) ) ||
( ( type == QskAspect::Metric ) && ( mask & QskSkinTransition::Metric ) ) )
{
auto value = oldTable.resolvedHint( aspect );
if ( value == nullptr && aspect.subControl() != QskAspect::Control )
{
auto a = aspect;
a.setSubControl( QskAspect::Control );
a.clearStates();
value = oldTable.resolvedHint( a );
}
2017-07-21 18:21:34 +02:00
/*
We are missing transitions, when a hint in newTable
gets resolved from QskControl. TODO ...
*/
if ( value && *value != entry.second )
candidates += AnimatorCandidate( aspect, *value, entry.second );
2017-07-21 18:21:34 +02:00
}
}
}
return candidates;
}
namespace
{
class AnimatorGroup final : public QObject
{
2017-07-21 18:21:34 +02:00
Q_OBJECT
public:
2017-07-21 18:21:34 +02:00
AnimatorGroup()
{
}
2017-07-21 18:21:34 +02:00
void start()
{
2017-07-23 16:36:40 +02:00
m_notifyConnection = QskAnimator::addAdvanceHandler( this,
SLOT( notify( QQuickWindow* ) ) );
for ( auto& it : m_hintAnimatorMap )
it.second.start();
for ( auto& it : m_graphicFilterAnimatorMap )
2017-07-21 18:21:34 +02:00
it.second.start();
}
bool isRunning() const
{
return !m_hintAnimatorMap.empty();
2017-07-21 18:21:34 +02:00
}
void reset()
{
2017-07-23 16:36:40 +02:00
disconnect( m_notifyConnection );
m_hintAnimatorMap.clear();
m_graphicFilterAnimatorMap.clear();
2017-07-21 18:21:34 +02:00
m_updateInfos.clear();
}
inline QVariant animatedHint( QskAspect::Aspect aspect ) const
{
auto it = m_hintAnimatorMap.find( aspect );
if ( it != m_hintAnimatorMap.cend() )
{
const auto& animator = it->second;
if ( animator.isRunning() )
return animator.currentValue();
}
return QVariant();
}
inline QVariant animatedGraphicFilter( int graphicRole ) const
{
auto it = m_graphicFilterAnimatorMap.find( graphicRole );
if ( it != m_graphicFilterAnimatorMap.cend() )
2017-07-21 18:21:34 +02:00
{
const auto& animator = it->second;
if ( animator.isRunning() )
return animator.currentValue();
}
return QVariant();
}
void addGraphicFilterAnimators(
QQuickWindow* window, const QskAnimationHint& animatorHint,
const std::unordered_map< int, QskColorFilter >& oldFilters,
const std::unordered_map< int, QskColorFilter >& newFilters )
{
const QskColorFilter noFilter;
for ( auto it2 = newFilters.begin(); it2 != newFilters.end(); ++it2 )
{
auto it1 = oldFilters.find( it2->first );
if ( it1 == oldFilters.cend() )
it1 = oldFilters.find( 0 );
const auto& f1 = ( it1 != oldFilters.cend() ) ? it1->second : noFilter;
const auto& f2 = it2->second;
if ( f1 != f2 )
{
QskVariantAnimator animator;
animator.setWindow( window );
animator.setDuration( animatorHint.duration );
animator.setEasingCurve( animatorHint.type );
animator.setStartValue( QVariant::fromValue( f1 ) );
animator.setEndValue( QVariant::fromValue( f2 ) );
m_graphicFilterAnimatorMap.emplace( it2->first, animator );
}
}
}
2017-07-21 18:21:34 +02:00
void addAnimators( QQuickItem* item,
const QskAnimationHint& animatorHint,
const QVector< AnimatorCandidate >& candidates, QskSkin* skin )
{
if ( !item->isVisible() )
return;
if ( auto control = qobject_cast< QskControl* >( item ) )
{
if ( control->isInitiallyPainted() && ( skin == control->effectiveSkin() ) )
{
2017-07-21 18:21:34 +02:00
addControlAnimators( control, animatorHint, candidates );
#if 1
/*
As it is hard to identify which controls depend on the animated
graphic filters we schedule an initial update and let the
controls do the rest: see QskSkinnable::effectiveGraphicFilter
*/
control->update();
#endif
}
2017-07-21 18:21:34 +02:00
}
2017-10-30 14:38:30 +01:00
const auto children = item->childItems();
for ( auto child : children )
2017-07-21 18:21:34 +02:00
addAnimators( child, animatorHint, candidates, skin );
}
private Q_SLOTS:
void notify( QQuickWindow* window )
{
if ( m_updateInfos.empty() )
return;
for ( auto& info : m_updateInfos )
{
QskControl* control = info.control;
if ( control && control->window() == window )
{
if ( info.updateModes & UpdateInfo::Polish )
{
control->resetImplicitSize();
control->polish();
}
if ( info.updateModes & UpdateInfo::Update )
control->update();
}
}
if ( !m_hintAnimatorMap.empty() )
2017-07-21 18:21:34 +02:00
{
if ( !m_hintAnimatorMap.begin()->second.isRunning() )
2017-07-21 18:21:34 +02:00
reset();
}
}
private:
void addControlAnimators( QskControl* control, const QskAnimationHint& animatorHint,
const QVector< AnimatorCandidate >& candidates )
{
const auto subControls = control->subControls();
2017-10-30 08:33:43 +01:00
for ( const auto& candidate : candidates )
2017-07-21 18:21:34 +02:00
{
using namespace QskAspect;
if ( candidate.aspect.type() != Metric )
{
if ( !( control->flags() & QQuickItem::ItemHasContents ) )
{
// while metrics might have an effect on layouts, we
// can safely ignore others for controls without content
continue;
}
}
const Subcontrol subControl = candidate.aspect.subControl();
if ( subControl != control->effectiveSubcontrol( subControl ) )
{
// The control uses subcontrol redirection, so we can assume it
// is not interested in this subcontrol.
continue;
}
if ( subControl != QskAspect::Control )
{
if ( !subControls.contains( subControl ) )
{
// the control is not interested in the aspect
continue;
}
}
else
{
if ( !control->autoFillBackground() )
{
// no need to animate the background unless we show it
continue;
2017-07-21 18:21:34 +02:00
}
}
QskAspect::Aspect a = candidate.aspect;
a.clearStates();
a.addState( control->skinState() );
const QskSkinHintStatus requestState = control->hintStatus( a );
if ( requestState.source != QskSkinHintStatus::Skin )
{
// The control does not resolve the aspect from the skin.
continue;
}
if ( candidate.aspect != requestState.aspect )
{
// the aspect was resolved to something else
continue;
}
addAnimator( control->window(), candidate, animatorHint );
2017-07-21 18:21:34 +02:00
storeUpdateInfo( control, candidate.aspect );
}
}
void addAnimator( QQuickWindow* window,
2017-07-21 18:21:34 +02:00
const AnimatorCandidate& candidate, QskAnimationHint animationHint )
{
auto it = m_hintAnimatorMap.find( candidate.aspect );
if ( it != m_hintAnimatorMap.end() )
2017-07-21 18:21:34 +02:00
return; // already there
it = m_hintAnimatorMap.emplace( candidate.aspect, QskHintAnimator() ).first;
2017-07-21 18:21:34 +02:00
auto& animator = it->second;
animator.setAspect( candidate.aspect );
animator.setStartValue( candidate.from );
animator.setEndValue( candidate.to );
animator.setDuration( animationHint.duration );
animator.setEasingCurve( animationHint.type );
animator.setControl( nullptr );
animator.setWindow( window );
2017-07-21 18:21:34 +02:00
}
inline void storeUpdateInfo( QskControl* control, QskAspect::Aspect aspect )
{
UpdateInfo info;
info.control = control;
info.updateModes = UpdateInfo::Update;
if ( aspect.type() == QskAspect::Metric )
info.updateModes |= UpdateInfo::Polish;
auto it = std::lower_bound(
m_updateInfos.begin(), m_updateInfos.end(), info, UpdateInfo::compare );
if ( ( it != m_updateInfos.end() ) && ( it->control == info.control ) )
it->updateModes |= info.updateModes;
else
m_updateInfos.insert( it, info );
}
std::unordered_map< QskAspect::Aspect, QskHintAnimator > m_hintAnimatorMap;
std::unordered_map< int, QskVariantAnimator > m_graphicFilterAnimatorMap;
2017-07-21 18:21:34 +02:00
std::vector< UpdateInfo > m_updateInfos; // vector: for fast iteration
2017-07-23 16:36:40 +02:00
QMetaObject::Connection m_notifyConnection;
2017-07-21 18:21:34 +02:00
};
}
Q_GLOBAL_STATIC( AnimatorGroup, qskSkinAnimator )
QskSkinTransition::QskSkinTransition():
m_mask( QskSkinTransition::AllTypes )
{
m_skins[0] = m_skins[1] = nullptr;
}
QskSkinTransition::~QskSkinTransition()
{
}
void QskSkinTransition::setMask( Type type )
{
m_mask = type;
}
QskSkinTransition::Type QskSkinTransition::mask() const
{
return m_mask;
}
void QskSkinTransition::setSourceSkin( QskSkin* skin )
{
m_skins[0] = skin;
}
QskSkin* QskSkinTransition::sourceSkin() const
{
return m_skins[0];
}
void QskSkinTransition::setTargetSkin( QskSkin* skin )
{
m_skins[1] = skin;
}
QskSkin* QskSkinTransition::targetSkin() const
{
return m_skins[1];
}
void QskSkinTransition::setAnimation( QskAnimationHint animationHint )
2017-07-21 18:21:34 +02:00
{
m_animationHint = animationHint;
2017-07-21 18:21:34 +02:00
}
QskAnimationHint QskSkinTransition::animation() const
{
return m_animationHint;
2017-07-21 18:21:34 +02:00
}
void QskSkinTransition::updateSkin( QskSkin*, QskSkin* )
{
// nop
}
2017-07-21 18:21:34 +02:00
void QskSkinTransition::process()
{
if ( ( m_skins[0] == nullptr ) || ( m_skins[1] == nullptr ) )
{
// do nothing
return;
}
qskSkinAnimator->reset();
if ( ( m_animationHint.duration <= 0 ) || ( m_mask == 0 ) )
2017-07-21 18:21:34 +02:00
{
// no animations, we can apply the changes
updateSkin( m_skins[0], m_skins[1] );
return;
}
QVector< AnimatorCandidate > candidates;
const auto oldFilters = m_skins[0]->graphicFilters();
2017-07-21 18:21:34 +02:00
{
// copy out all hints before updating the skin
// - would be good to have Copy on Write here
const auto oldTable = m_skins[0]->hintTable();
2017-07-21 18:21:34 +02:00
// apply the changes
updateSkin( m_skins[0], m_skins[1] );
candidates = qskAnimatorCandidates( m_mask, oldTable, oldFilters,
m_skins[1]->hintTable(), m_skins[1]->graphicFilters() );
2017-07-21 18:21:34 +02:00
}
if ( !candidates.isEmpty() )
{
bool doGraphicFilter = m_mask & QskSkinTransition::Color;
2017-07-21 18:21:34 +02:00
2017-10-30 14:38:30 +01:00
const auto windows = qGuiApp->topLevelWindows();
for ( const auto window : windows )
2017-07-21 18:21:34 +02:00
{
if ( auto quickWindow = qobject_cast< QQuickWindow* >( window ) )
2017-07-21 18:21:34 +02:00
{
if ( doGraphicFilter )
{
qskSkinAnimator->addGraphicFilterAnimators(
quickWindow, m_animationHint,
oldFilters, m_skins[1]->graphicFilters() );
doGraphicFilter = false;
}
/*
finally we schedule the animators the hard way by running
over the the item trees.
*/
2017-07-21 18:21:34 +02:00
qskSkinAnimator->addAnimators( quickWindow->contentItem(),
m_animationHint, candidates, m_skins[1] );
2017-07-21 18:21:34 +02:00
}
}
qskSkinAnimator->start();
}
}
bool QskSkinTransition::isRunning()
{
return qskSkinAnimator.exists() && qskSkinAnimator->isRunning();
}
QVariant QskSkinTransition::animatedHint( QskAspect::Aspect aspect )
{
if ( !qskSkinAnimator.exists() )
return QVariant();
return qskSkinAnimator->animatedHint( aspect );
}
QVariant QskSkinTransition::animatedGraphicFilter( int graphicRole )
{
if ( !qskSkinAnimator.exists() )
return QVariant();
return qskSkinAnimator->animatedGraphicFilter( graphicRole );
}
2017-07-21 18:21:34 +02:00
#include "QskSkinTransition.moc"