2017-07-21 18:21:34 +02:00
|
|
|
#include "QskSkinTransition.h"
|
|
|
|
#include "QskControl.h"
|
|
|
|
#include "QskSkin.h"
|
2017-08-22 20:50:55 +02:00
|
|
|
#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,
|
2017-10-17 17:34:00 +02:00
|
|
|
const QskSkinHintTable& oldTable,
|
2017-07-21 18:21:34 +02:00
|
|
|
const std::unordered_map< int, QskColorFilter >& oldFilters,
|
2017-10-17 17:34:00 +02:00
|
|
|
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
|
|
|
|
|
2017-07-26 17:48:52 +02:00
|
|
|
const QskColorFilter noFilter;
|
2017-07-21 18:21:34 +02:00
|
|
|
QVector< AnimatorCandidate > candidates;
|
|
|
|
|
2017-10-17 17:34:00 +02:00
|
|
|
if ( !oldTable.hasHints() )
|
2017-07-21 18:21:34 +02:00
|
|
|
return candidates;
|
|
|
|
|
2017-10-17 17:34:00 +02:00
|
|
|
for ( 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;
|
|
|
|
|
|
|
|
const auto it1 = oldFilters.find( entry.second.toInt() );
|
|
|
|
const auto it2 = newFilters.find( entry.second.toInt() );
|
|
|
|
|
2017-07-26 17:48:52 +02:00
|
|
|
if ( it1 != oldFilters.end() || it2 != newFilters.end() )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
2017-07-26 17:48:52 +02:00
|
|
|
const auto& f1 = ( it1 != oldFilters.end() ) ? it1->second : noFilter;
|
|
|
|
const auto& f2 = ( it2 != newFilters.end() ) ? it2->second : noFilter;
|
2017-07-21 18:21:34 +02:00
|
|
|
|
2017-07-26 17:48:52 +02:00
|
|
|
if ( f1 != f2 )
|
2017-07-21 18:21:34 +02:00
|
|
|
{
|
|
|
|
candidates += AnimatorCandidate( aspect,
|
2017-07-26 17:48:52 +02:00
|
|
|
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 ) ) )
|
|
|
|
{
|
2017-10-17 17:34:00 +02:00
|
|
|
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
|
|
|
|
2017-10-17 17:34:00 +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
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
AnimatorGroup()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void start()
|
|
|
|
{
|
2017-07-23 16:36:40 +02:00
|
|
|
m_notifyConnection = QskAnimator::addAdvanceHandler( this,
|
|
|
|
SLOT( notify( QQuickWindow* ) ) );
|
|
|
|
|
|
|
|
for ( auto& it : m_map )
|
2017-07-21 18:21:34 +02:00
|
|
|
it.second.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isRunning() const
|
|
|
|
{
|
|
|
|
return !m_map.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset()
|
|
|
|
{
|
2017-07-23 16:36:40 +02:00
|
|
|
disconnect( m_notifyConnection );
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
m_map.clear();
|
|
|
|
m_updateInfos.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline QVariant animatedHint( QskAspect::Aspect aspect ) const
|
|
|
|
{
|
|
|
|
auto it = m_map.find( aspect );
|
|
|
|
if ( it != m_map.cend() )
|
|
|
|
{
|
|
|
|
const auto& animator = it->second;
|
|
|
|
if ( animator.isRunning() )
|
|
|
|
return animator.currentValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
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() ) )
|
|
|
|
addControlAnimators( control, animatorHint, candidates );
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( auto child : item->childItems() )
|
|
|
|
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_map.empty() )
|
|
|
|
{
|
|
|
|
if ( !m_map.begin()->second.isRunning() )
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void addControlAnimators( QskControl* control, const QskAnimationHint& animatorHint,
|
|
|
|
const QVector< AnimatorCandidate >& candidates )
|
|
|
|
{
|
|
|
|
const auto subControls = control->subControls();
|
|
|
|
|
|
|
|
for ( auto candidate : candidates )
|
|
|
|
{
|
|
|
|
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() )
|
|
|
|
{
|
2017-10-17 17:34:00 +02:00
|
|
|
// no need to animate the background unless we show it
|
|
|
|
continue;
|
2017-07-21 18:21:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-26 13:21:44 +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;
|
|
|
|
}
|
|
|
|
|
2017-07-21 18:21:34 +02:00
|
|
|
addAnimator( control, candidate, animatorHint );
|
|
|
|
storeUpdateInfo( control, candidate.aspect );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addAnimator( QskControl* control,
|
|
|
|
const AnimatorCandidate& candidate, QskAnimationHint animationHint )
|
|
|
|
{
|
|
|
|
auto it = m_map.find( candidate.aspect );
|
|
|
|
if ( it != m_map.end() )
|
|
|
|
return; // already there
|
|
|
|
|
|
|
|
it = m_map.emplace( candidate.aspect, QskHintAnimator() ).first;
|
|
|
|
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( control->window() );
|
|
|
|
}
|
|
|
|
|
|
|
|
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::map< QskAspect::Aspect, QskHintAnimator > m_map;
|
|
|
|
std::vector< UpdateInfo > m_updateInfos; // vector: for fast iteration
|
2017-07-23 16:36:40 +02:00
|
|
|
|
2017-10-17 17:34:00 +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 animation )
|
|
|
|
{
|
|
|
|
m_animation = animation;
|
|
|
|
}
|
|
|
|
|
|
|
|
QskAnimationHint QskSkinTransition::animation() const
|
|
|
|
{
|
|
|
|
return m_animation;
|
|
|
|
}
|
|
|
|
|
2017-07-25 21:32:12 +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();
|
|
|
|
|
2017-10-30 08:08:58 +01:00
|
|
|
if ( ( m_animation.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;
|
|
|
|
|
|
|
|
{
|
|
|
|
// copy out all hints before updating the skin
|
|
|
|
// - would be good to have Copy on Write here
|
|
|
|
|
2017-10-17 17:34:00 +02:00
|
|
|
const auto oldTable = m_skins[0]->hintTable();
|
2017-07-21 18:21:34 +02:00
|
|
|
const auto oldFilters = m_skins[0]->graphicFilters();
|
|
|
|
|
|
|
|
// apply the changes
|
|
|
|
updateSkin( m_skins[0], m_skins[1] );
|
|
|
|
|
2017-10-17 17:34:00 +02:00
|
|
|
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() )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
finally we schedule the animators the hard way by running
|
|
|
|
over the the item trees. for the moment o.k. but we should
|
|
|
|
find a way not to create lots of identical animator objects for
|
|
|
|
each object.
|
|
|
|
*/
|
|
|
|
|
|
|
|
for ( const auto window : qGuiApp->topLevelWindows() )
|
|
|
|
{
|
|
|
|
if ( auto quickWindow = qobject_cast< const QQuickWindow* >( window ) )
|
|
|
|
{
|
|
|
|
qskSkinAnimator->addAnimators( quickWindow->contentItem(),
|
|
|
|
m_animation, candidates, m_skins[1] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "QskSkinTransition.moc"
|