skin transition heavily changed

This commit is contained in:
Uwe Rathmann 2022-03-31 18:09:03 +02:00
parent 27ee0fe423
commit e2d6823927
6 changed files with 590 additions and 572 deletions

View File

@ -64,41 +64,11 @@ QskSkinHintTable::QskSkinHintTable()
{
}
QskSkinHintTable::QskSkinHintTable( const QskSkinHintTable& other )
: m_hints( nullptr )
, m_animatorCount( other.m_animatorCount )
, m_states( other.m_states )
{
if ( other.m_hints )
m_hints = new HintMap( *( other.m_hints ) );
}
QskSkinHintTable::~QskSkinHintTable()
{
delete m_hints;
}
QskSkinHintTable& QskSkinHintTable::operator=( const QskSkinHintTable& other )
{
m_animatorCount = other.m_animatorCount;
m_states = other.m_states;
if ( other.m_hints )
{
if ( m_hints == nullptr )
m_hints = new HintMap();
*m_hints = *other.m_hints;
}
else
{
delete m_hints;
m_hints = nullptr;
}
return *this;
}
const std::unordered_map< QskAspect, QVariant >& QskSkinHintTable::hints() const
{
if ( m_hints )

View File

@ -17,12 +17,8 @@ class QSK_EXPORT QskSkinHintTable
{
public:
QskSkinHintTable();
QskSkinHintTable( const QskSkinHintTable& other );
~QskSkinHintTable();
QskSkinHintTable& operator=( const QskSkinHintTable& );
bool setAnimation( QskAspect, QskAnimationHint );
QskAnimationHint animation( QskAspect ) const;
@ -57,6 +53,8 @@ class QSK_EXPORT QskSkinHintTable
bool isResolutionMatching( QskAspect, QskAspect ) const;
private:
Q_DISABLE_COPY( QskSkinHintTable )
static const QVariant invalidHint;
typedef std::unordered_map< QskAspect, QVariant > HintMap;

View File

@ -20,6 +20,51 @@
#include <unordered_map>
#include <vector>
static void qskAddCandidates( const QskSkinTransition::Type mask,
const QskSkin* skin, QSet< QskAspect >& candidates )
{
for ( const auto& entry : skin->hintTable().hints() )
{
const auto aspect = entry.first.trunk();
if ( aspect.isAnimator() )
continue;
bool isCandidate = false;
switch( aspect.type() )
{
case QskAspect::Flag:
{
if ( aspect.flagPrimitive() == QskAspect::GraphicRole )
{
isCandidate = mask & QskSkinTransition::Color;
}
#if 0
else if ( aspect.flagPrimitive() == QskAspect::FontRole )
{
isCandidate = mask & QskSkinTransition::Metric;
}
#endif
break;
}
case QskAspect::Color:
{
isCandidate = mask & QskSkinTransition::Color;
break;
}
case QskAspect::Metric:
{
isCandidate = mask & QskSkinTransition::Metric;
break;
}
}
if ( isCandidate )
candidates += aspect;
}
}
namespace
{
class UpdateInfo
@ -40,157 +85,125 @@ namespace
int updateModes;
};
class AnimatorCandidate
class HintAnimator : public QskHintAnimator
{
public:
AnimatorCandidate() = default;
inline AnimatorCandidate( QskAspect aspect,
const QVariant& from, const QVariant& to )
: aspect( aspect )
, from( from )
, to( to )
inline HintAnimator( const QskControl* control, const QskAspect aspect,
const QVariant& value1, const QVariant& value2, QskAnimationHint hint )
{
}
setAspect( aspect );
setStartValue( value1 );
setEndValue( value2 );
QskAspect aspect;
QVariant from;
QVariant to;
setDuration( hint.duration );
setEasingCurve( hint.type );
setUpdateFlags( hint.updateFlags );
setWindow( control->window() );
}
};
class WindowAnimator
{
public:
WindowAnimator( QQuickWindow* = nullptr );
const QQuickWindow* window() const;
void start();
bool isRunning() const;
QVariant animatedHint( QskAspect ) const;
QVariant animatedGraphicFilter( int graphicRole ) const;
void addGraphicFilterAnimators( const QskAnimationHint&,
const QskSkin*, const QskSkin* );
void addItemAspects( QQuickItem*,
const QskAnimationHint&, const QSet< QskAspect >&,
const QskSkin*, const QskSkin* );
void update();
private:
bool isControlAffected( const QskControl*,
const QVector< QskAspect::Subcontrol >&, QskAspect ) const;
void addHints( const QskControl*,
const QskAnimationHint&, const QSet< QskAspect >& candidates,
const QskSkin* skin1, const QskSkin* skin2 );
void storeAnimator( const QskControl*, const QskAspect,
const QVariant&, const QVariant&, QskAnimationHint );
void storeUpdateInfo( const QskControl*, QskAspect );
QQuickWindow* m_window;
std::unordered_map< QskAspect, HintAnimator > m_animatorMap;
std::unordered_map< int, QskVariantAnimator > m_graphicFilterAnimatorMap;
std::vector< UpdateInfo > m_updateInfos; // vector: for fast iteration
};
class ApplicationAnimator : public QObject
{
Q_OBJECT
public:
~ApplicationAnimator();
WindowAnimator* windowAnimator( const QQuickWindow* );
void add( WindowAnimator* );
void start();
void reset();
bool isRunning() const;
private Q_SLOTS:
// using functor slots ?
void notify( QQuickWindow* );
void cleanup( QQuickWindow* );
private:
/*
It should be possible to find an implementation, that interpolates
a skin hint only once for all windows. But as our animtors are driven by
QQuickWindow::afterAnimating the code will have to be somehow tricky.
But as skin transitions are no operations, that happen often, we can accept
the overhaed of the current implementation and do the finetuning later.
*/
std::vector< WindowAnimator* > m_windowAnimators;
QMetaObject::Connection m_connections[2];
};
}
static QVector< AnimatorCandidate > qskAnimatorCandidates(
QskSkinTransition::Type mask,
const QskSkinHintTable& oldTable,
const std::unordered_map< int, QskColorFilter >& oldFilters,
const QskSkinHintTable& newTable,
const std::unordered_map< int, QskColorFilter >& newFilters )
Q_GLOBAL_STATIC( ApplicationAnimator, qskApplicationAnimator )
WindowAnimator::WindowAnimator( QQuickWindow* window )
: m_window( window )
{
// building a list of candidates for animations by comparing
// the old/new set of skin hints
const QskColorFilter noFilter;
QVector< AnimatorCandidate > candidates;
if ( !oldTable.hasHints() )
return candidates;
for ( const auto& entry : newTable.hints() )
{
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;
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 )
{
const auto it1 = oldFilters.find( role1 );
const auto it2 = newFilters.find( role2 );
if ( it1 != oldFilters.end() || it2 != newFilters.end() )
{
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 ) );
}
}
}
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 );
}
/*
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 );
}
}
}
return candidates;
}
namespace
inline const QQuickWindow* WindowAnimator::window() const
{
class AnimatorGroup
{
public:
AnimatorGroup( QQuickWindow* window = nullptr )
: m_window( window )
{
}
inline const QQuickWindow* window() const
{
return m_window;
}
}
void start()
{
for ( auto& it : m_hintAnimatorMap )
void WindowAnimator::start()
{
for ( auto& it : m_animatorMap )
it.second.start();
for ( auto& it : m_graphicFilterAnimatorMap )
it.second.start();
}
}
bool isRunning() const
bool WindowAnimator::isRunning() const
{
if ( !m_animatorMap.empty() )
{
if ( !m_hintAnimatorMap.empty() )
{
const auto& animator = m_hintAnimatorMap.begin()->second;
const auto& animator = m_animatorMap.begin()->second;
if ( animator.isRunning() )
return true;
}
@ -203,12 +216,12 @@ namespace
}
return false;
}
}
inline QVariant animatedHint( QskAspect aspect ) const
{
auto it = m_hintAnimatorMap.find( aspect );
if ( it != m_hintAnimatorMap.cend() )
inline QVariant WindowAnimator::animatedHint( QskAspect aspect ) const
{
auto it = m_animatorMap.find( aspect );
if ( it != m_animatorMap.cend() )
{
const auto& animator = it->second;
if ( animator.isRunning() )
@ -216,10 +229,10 @@ namespace
}
return QVariant();
}
}
inline QVariant animatedGraphicFilter( int graphicRole ) const
{
inline QVariant WindowAnimator::animatedGraphicFilter( int graphicRole ) const
{
auto it = m_graphicFilterAnimatorMap.find( graphicRole );
if ( it != m_graphicFilterAnimatorMap.cend() )
{
@ -229,22 +242,23 @@ namespace
}
return QVariant();
}
}
void addGraphicFilterAnimators(
const QskAnimationHint& animatorHint,
const std::unordered_map< int, QskColorFilter >& oldFilters,
const std::unordered_map< int, QskColorFilter >& newFilters )
{
void WindowAnimator::addGraphicFilterAnimators( const QskAnimationHint& animatorHint,
const QskSkin* skin1, const QskSkin* skin2 )
{
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& filter1 = skin1->graphicFilters();
const auto& filter2 = skin2->graphicFilters();
const auto& f1 = ( it1 != oldFilters.cend() ) ? it1->second : noFilter;
for ( auto it2 = filter2.begin(); it2 != filter2.end(); ++it2 )
{
auto it1 = filter1.find( it2->first );
if ( it1 == filter1.cend() )
it1 = filter1.find( 0 );
const auto& f1 = ( it1 != filter1.cend() ) ? it1->second : noFilter;
const auto& f2 = it2->second;
if ( f1 != f2 )
@ -259,19 +273,20 @@ namespace
m_graphicFilterAnimatorMap.emplace( it2->first, animator );
}
}
}
}
void addAnimators( QQuickItem* item, const QskAnimationHint& animatorHint,
const QVector< AnimatorCandidate >& candidates, QskSkin* skin )
{
void WindowAnimator::addItemAspects( QQuickItem* item,
const QskAnimationHint& animatorHint, const QSet< QskAspect >& candidates,
const QskSkin* skin1, const QskSkin* skin2 )
{
if ( !item->isVisible() )
return;
if ( auto control = qskControlCast( item ) )
{
if ( control->isInitiallyPainted() && ( skin == control->effectiveSkin() ) )
if ( control->isInitiallyPainted() && ( control->effectiveSkin() == skin2 ) )
{
addControlAnimators( control, animatorHint, candidates );
addHints( control, animatorHint, candidates, skin1, skin2 );
#if 1
/*
As it is hard to identify which controls depend on the animated
@ -285,11 +300,11 @@ namespace
const auto children = item->childItems();
for ( auto child : children )
addAnimators( child, animatorHint, candidates, skin );
}
addItemAspects( child, animatorHint, candidates, skin1, skin2 );
}
void update()
{
void WindowAnimator::update()
{
for ( auto& info : m_updateInfos )
{
if ( auto control = info.control )
@ -304,26 +319,81 @@ namespace
control->update();
}
}
}
}
private:
void addControlAnimators( QskControl* control, const QskAnimationHint& animatorHint,
const QVector< AnimatorCandidate >& candidates )
{
void WindowAnimator::addHints( const QskControl* control,
const QskAnimationHint& animatorHint, const QSet< QskAspect >& candidates,
const QskSkin* skin1, const QskSkin* skin2 )
{
const auto subControls = control->subControls();
for ( const auto& candidate : candidates )
{
const auto aspect = candidate.aspect;
const auto& localTable = control->hintTable();
const auto& table1 = skin1->hintTable();
const auto& table2 = skin2->hintTable();
for ( auto aspect : candidates )
{
if ( !isControlAffected( control, subControls, aspect ) )
continue;
aspect.setPlacement( control->effectivePlacement() );
aspect.setStates( control->skinStates() );
if ( localTable.resolvedHint( aspect ) )
{
// value is not from the skin - ignored
continue;
}
QskAspect r1, r2;
const auto v1 = table1.resolvedHint( aspect, &r1 );
const auto v2 = table2.resolvedHint( aspect, &r2 );
if ( v1 && v2 )
{
if ( QskVariantAnimator::maybeInterpolate( *v1, *v2 ) )
{
if ( r1.placement() == r2.placement() )
aspect.setPlacement( r2.placement() );
if ( r1.states() == r2.states() )
aspect.setStates( r2.states() );
storeAnimator( control, aspect, *v1, *v2, animatorHint );
storeUpdateInfo( control, aspect );
}
}
else if ( v1 )
{
aspect.setPlacement( r1.placement() );
aspect.setStates( r1.states() );
storeAnimator( control, aspect, *v1, QVariant(), animatorHint );
storeUpdateInfo( control, aspect );
}
else if ( v2 )
{
aspect.setPlacement( r1.placement() );
aspect.setStates( r1.states() );
storeAnimator( control, aspect, QVariant(), *v2, animatorHint );
storeUpdateInfo( control, aspect );
}
}
}
inline bool WindowAnimator::isControlAffected( const QskControl* control,
const QVector< QskAspect::Subcontrol >& subControls, const QskAspect aspect ) const
{
if ( !aspect.isMetric() )
{
if ( !( control->flags() & QQuickItem::ItemHasContents ) )
{
// while metrics might have an effect on layouts, we
// can safely ignore others for controls without content
continue;
// ignore all others for controls without content
return false;
}
}
@ -332,76 +402,44 @@ namespace
{
// The control uses subcontrol redirection, so we can assume it
// is not interested in this subcontrol.
continue;
return false;
}
if ( subControl != QskAspect::Control )
{
if ( !subControls.contains( subControl ) )
{
// the control is not interested in the aspect
continue;
}
}
else
if ( subControl == QskAspect::Control )
{
if ( !control->autoFillBackground() )
{
// no need to animate the background unless we show it
continue;
return false;
}
}
auto a = aspect;
a.setStates( control->skinStates() );
const auto requestState = control->hintStatus( a );
if ( requestState.source != QskSkinHintStatus::Skin )
else
{
// The control does not resolve the aspect from the skin.
continue;
}
if ( aspect != requestState.aspect )
if ( !subControls.contains( subControl ) )
{
// the aspect was resolved to something else
continue;
}
addAnimator( control->window(), aspect,
candidate.from, candidate.to, animatorHint );
storeUpdateInfo( control, aspect );
// the control is not interested in the aspect
return false;
}
}
void addAnimator( QQuickWindow* window, const QskAspect aspect,
const QVariant& from, const QVariant& to, QskAnimationHint animationHint )
return true;
}
inline void WindowAnimator::storeAnimator( const QskControl* control, const QskAspect aspect,
const QVariant& value1, const QVariant& value2, QskAnimationHint hint )
{
if ( m_animatorMap.find( aspect ) == m_animatorMap.cend() )
{
auto it = m_hintAnimatorMap.find( aspect );
if ( it != m_hintAnimatorMap.end() )
return; // already there
it = m_hintAnimatorMap.emplace( aspect, QskHintAnimator() ).first;
auto& animator = it->second;
animator.setAspect( aspect );
animator.setStartValue( from );
animator.setEndValue( to );
animator.setDuration( animationHint.duration );
animator.setEasingCurve( animationHint.type );
animator.setUpdateFlags( animationHint.updateFlags );
animator.setControl( nullptr );
animator.setWindow( window );
m_animatorMap.emplace( aspect,
HintAnimator( control, aspect, value1, value2, hint ) );
}
}
inline void storeUpdateInfo( QskControl* control, QskAspect aspect )
{
inline void WindowAnimator::storeUpdateInfo( const QskControl* control, QskAspect aspect )
{
UpdateInfo info;
info.control = control;
info.control = const_cast< QskControl* >( control );
info.updateModes = UpdateInfo::Update;
if ( aspect.isMetric() )
@ -414,121 +452,94 @@ namespace
it->updateModes |= info.updateModes;
else
m_updateInfos.insert( it, info );
}
}
QQuickWindow* m_window;
std::unordered_map< QskAspect, QskHintAnimator > m_hintAnimatorMap;
std::unordered_map< int, QskVariantAnimator > m_graphicFilterAnimatorMap;
std::vector< UpdateInfo > m_updateInfos; // vector: for fast iteration
};
class AnimatorGroups : public QObject
{
Q_OBJECT
public:
~AnimatorGroups()
{
ApplicationAnimator::~ApplicationAnimator()
{
reset();
}
}
inline AnimatorGroup* animatorGroup( const QQuickWindow* window )
inline WindowAnimator* ApplicationAnimator::windowAnimator( const QQuickWindow* window )
{
if ( window )
{
if ( !m_animatorGroups.empty() && window )
for ( auto animator : m_windowAnimators )
{
for ( auto group : m_animatorGroups )
{
if ( group->window() == window )
return group;
if ( animator->window() == window )
return animator;
}
}
return nullptr;
}
}
void add( AnimatorGroup* group )
{
m_animatorGroups.push_back( group );
}
void ApplicationAnimator::add( WindowAnimator* animator )
{
m_windowAnimators.push_back( animator );
}
void start()
{
void ApplicationAnimator::start()
{
m_connections[0] = QskAnimator::addAdvanceHandler(
this, SLOT(notify(QQuickWindow*)), Qt::UniqueConnection );
m_connections[1] = QskAnimator::addCleanupHandler(
this, SLOT(cleanup(QQuickWindow*)), Qt::UniqueConnection );
for ( auto& group : m_animatorGroups )
group->start();
}
for ( auto& animator : m_windowAnimators )
animator->start();
}
void reset()
{
qDeleteAll( m_animatorGroups );
m_animatorGroups.clear();
void ApplicationAnimator::reset()
{
qDeleteAll( m_windowAnimators );
m_windowAnimators.clear();
disconnect( m_connections[0] );
disconnect( m_connections[1] );
}
}
inline bool isRunning() const
{
return !m_animatorGroups.empty();
}
inline bool ApplicationAnimator::isRunning() const
{
return !m_windowAnimators.empty();
}
private Q_SLOTS:
void notify( QQuickWindow* window )
void ApplicationAnimator::notify( QQuickWindow* window )
{
for ( auto& animator : m_windowAnimators )
{
for ( auto& group : m_animatorGroups )
if ( animator->window() == window )
{
if ( group->window() == window )
{
group->update();
animator->update();
return;
}
}
}
}
void cleanup( QQuickWindow* window )
void ApplicationAnimator::cleanup( QQuickWindow* window )
{
for ( auto it = m_windowAnimators.begin();
it != m_windowAnimators.end(); ++it )
{
for ( auto it = m_animatorGroups.begin();
it != m_animatorGroups.end(); ++it )
auto animator = *it;
if ( animator->window() == window )
{
auto group = *it;
if ( group->window() == window )
{
if ( !group->isRunning() )
if ( !animator->isRunning() )
{
// The notification might be for other animators
m_animatorGroups.erase( it );
delete group;
m_windowAnimators.erase( it );
delete animator;
}
break;
}
}
if ( m_animatorGroups.empty() )
if ( m_windowAnimators.empty() )
reset();
}
private:
/*
It should be possible to find an implementation, that interpolates
a skin hint only once for all windows. But as our animtors are driven by
QQuickWindow::afterAnimating the code will have to be somehow tricky.
But as skin transitions are no operations, that happen often, we can accept
the overhaed of the current implementation and do the finetuning later.
*/
std::vector< AnimatorGroup* > m_animatorGroups;
QMetaObject::Connection m_connections[2];
};
}
Q_GLOBAL_STATIC( AnimatorGroups, qskSkinAnimator )
class QskSkinTransition::PrivateData
{
public:
@ -593,38 +604,20 @@ void QskSkinTransition::updateSkin( QskSkin*, QskSkin* )
void QskSkinTransition::process()
{
auto skinFrom = m_data->skins[ 0 ];
auto skinTo = m_data->skins[ 1 ];
qskApplicationAnimator->reset();
if ( ( skinFrom == nullptr ) || ( skinTo == nullptr ) )
auto skin1 = m_data->skins[ 0 ];
auto skin2 = m_data->skins[ 1 ];
QSet< QskAspect > candidates;
if ( skin1 && skin2 )
{
// do nothing
return;
if ( ( m_data->animationHint.duration > 0 ) && ( m_data->mask != 0 ) )
{
qskAddCandidates( m_data->mask, skin1, candidates );
qskAddCandidates( m_data->mask, skin2, candidates );
}
qskSkinAnimator->reset();
if ( ( m_data->animationHint.duration <= 0 ) || ( m_data->mask == 0 ) )
{
// no animations, we can apply the changes
updateSkin( skinFrom, skinTo );
return;
}
QVector< AnimatorCandidate > candidates;
const auto oldFilters = skinFrom->graphicFilters();
{
// copy out all hints before updating the skin
// - would be good to have Copy on Write here
const auto oldTable = skinFrom->hintTable();
// apply the changes
updateSkin( skinFrom, skinTo );
candidates = qskAnimatorCandidates( m_data->mask, oldTable, oldFilters,
skinTo->hintTable(), skinTo->graphicFilters() );
}
if ( !candidates.isEmpty() )
@ -635,21 +628,19 @@ void QskSkinTransition::process()
for ( const auto window : windows )
{
if ( auto quickWindow = qobject_cast< QQuickWindow* >( window ) )
if ( auto w = qobject_cast< QQuickWindow* >( window ) )
{
if ( !quickWindow->isVisible() ||
( qskEffectiveSkin( quickWindow ) != skinTo ) )
if ( !w->isVisible() || ( qskEffectiveSkin( w ) != skin2 ) )
{
continue;
}
auto group = new AnimatorGroup( quickWindow );
auto animator = new WindowAnimator( w );
if ( doGraphicFilter )
{
group->addGraphicFilterAnimators(
m_data->animationHint, oldFilters,
skinTo->graphicFilters() );
animator->addGraphicFilterAnimators(
m_data->animationHint, skin1, skin2 );
doGraphicFilter = false;
}
@ -659,21 +650,24 @@ void QskSkinTransition::process()
over the the item trees.
*/
group->addAnimators( quickWindow->contentItem(),
m_data->animationHint, candidates, skinTo );
animator->addItemAspects( w->contentItem(),
m_data->animationHint, candidates, skin1, skin2 );
qskSkinAnimator->add( group );
qskApplicationAnimator->add( animator );
}
}
qskSkinAnimator->start();
qskApplicationAnimator->start();
}
// apply the changes
updateSkin( skin1, skin2 );
}
bool QskSkinTransition::isRunning()
{
if ( qskSkinAnimator.exists() )
return qskSkinAnimator->isRunning();
if ( qskApplicationAnimator.exists() )
return qskApplicationAnimator->isRunning();
return false;
}
@ -681,10 +675,10 @@ bool QskSkinTransition::isRunning()
QVariant QskSkinTransition::animatedHint(
const QQuickWindow* window, QskAspect aspect )
{
if ( qskSkinAnimator.exists() )
if ( qskApplicationAnimator.exists() )
{
if ( const auto group = qskSkinAnimator->animatorGroup( window ) )
return group->animatedHint( aspect );
if ( const auto animator = qskApplicationAnimator->windowAnimator( window ) )
return animator->animatedHint( aspect );
}
return QVariant();
@ -693,10 +687,10 @@ QVariant QskSkinTransition::animatedHint(
QVariant QskSkinTransition::animatedGraphicFilter(
const QQuickWindow* window, int graphicRole )
{
if ( qskSkinAnimator.exists() )
if ( qskApplicationAnimator.exists() )
{
if ( const auto group = qskSkinAnimator->animatorGroup( window ) )
return group->animatedGraphicFilter( graphicRole );
if ( const auto animator = qskApplicationAnimator->windowAnimator( window ) )
return animator->animatedGraphicFilter( graphicRole );
}
return QVariant();

View File

@ -38,16 +38,6 @@ static inline bool qskIsControl( const QskSkinnable* skinnable )
return skinnable->metaObject()->inherits( &QskControl::staticMetaObject );
}
static inline QVariant qskTypedNullValue( const QVariant& value )
{
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
return QVariant( value.metaType() );
#else
return QVariant( value.userType(), nullptr );
#endif
}
static inline bool qskSetFlag( QskSkinnable* skinnable,
const QskAspect aspect, int flag )
{
@ -1155,27 +1145,11 @@ void QskSkinnable::startHintTransition( QskAspect aspect,
if ( control->window() == nullptr || !isTransitionAccepted( aspect ) )
return;
/*
We might be invalid for one of the values, when an aspect
has not been defined for all states ( f.e. metrics are expected
to fallback to 0.0 ). In this case we create a default one.
*/
auto v1 = from;
auto v2 = to;
if ( !v1.isValid() )
{
v1 = qskTypedNullValue( v2 );
}
else if ( !v2.isValid() )
{
v2 = qskTypedNullValue( v1 );
}
else if ( v1.userType() != v2.userType() )
{
if ( !QskVariantAnimator::convertValues( v1, v2 ) )
return;
}
if ( aspect.flagPrimitive() == QskAspect::GraphicRole )
{

View File

@ -76,6 +76,31 @@ QSK_DECL_INSANE static inline QVariant qskInterpolate(
return f( from.constData(), to.constData(), progress );
}
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
using QskMetaType = int;
static inline QskMetaType qskMetaType( const QVariant& v ) { return v.userType(); }
#else
using QskMetaType = QMetaType;
static inline QskMetaType qskMetaType( const QVariant& v ) { return v.metaType(); }
#endif
static inline QVariant qskDefaultVariant( QskMetaType type )
{
return QVariant( type, nullptr );
}
static inline QVariant qskConvertedVariant( const QVariant& from, QskMetaType type )
{
auto v = from;
v.convert( type );
return v;
}
QskVariantAnimator::QskVariantAnimator()
: m_interpolator( nullptr )
{
@ -100,35 +125,60 @@ void QskVariantAnimator::setCurrentValue( const QVariant& value )
m_currentValue = value;
}
bool QskVariantAnimator::convertValues( QVariant& v1, QVariant& v2 )
{
if ( !v1.isValid() && !v2.isValid() )
return false;
const auto type1 = qskMetaType( v1 );
const auto type2 = qskMetaType( v2 );
if ( !v1.isValid() )
{
v1 = qskDefaultVariant( type2 );
return true;
}
if ( !v2.isValid() )
{
v2 = qskDefaultVariant( type1 );
return true;
}
if ( type1 != type2 )
{
if ( v1.canConvert( type2 ) )
{
v1.convert( type2 );
return true;
}
if ( v2.canConvert( type1 ) )
{
v2.convert( type1 );
return true;
}
return false;
}
return true;
}
void QskVariantAnimator::setup()
{
m_interpolator = nullptr;
if ( m_startValue.userType() != m_endValue.userType() )
if ( convertValues( m_startValue, m_endValue ) )
{
/*
Convert one value so that the types are matching.
if ( m_startValue != m_endValue )
{
const auto id = m_startValue.userType();
As a side effect startValue()/endValue() won't return what had
been set setStartValue()/setEndValue() !
*/
if ( m_startValue.canConvert( m_endValue.userType() ) )
{
m_startValue.convert( m_endValue.userType() );
}
else if ( m_endValue.canConvert( m_startValue.userType() ) )
{
m_endValue.convert( m_startValue.userType() );
}
}
const auto type = m_startValue.userType();
if ( type == m_endValue.userType() )
{
// all what has been registered by qRegisterAnimationInterpolator
m_interpolator = reinterpret_cast< void ( * )() >(
QVariantAnimationPrivate::getInterpolator( type ) );
QVariantAnimationPrivate::getInterpolator( id ) );
}
}
m_currentValue = m_interpolator ? m_startValue : m_endValue;
@ -150,3 +200,32 @@ void QskVariantAnimator::done()
{
m_interpolator = nullptr;
}
bool QskVariantAnimator::maybeInterpolate(
const QVariant& value1, const QVariant& value2 )
{
if ( !value1.isValid() && !value2.isValid() )
return false;
const auto type1 = qskMetaType( value1 );
const auto type2 = qskMetaType( value2 );
if ( !value1.isValid() )
return value2 != qskDefaultVariant( type2 );
if ( !value2.isValid() )
return value1 != qskDefaultVariant( type1 );
if ( type1 != type2 )
{
if ( value1.canConvert( type2 ) )
return value2 != qskConvertedVariant( value1, type2 );
if ( value2.canConvert( type1 ) )
return value1 != qskConvertedVariant( value2, type1 );
return false;
}
return value1 != value2;
}

View File

@ -24,6 +24,9 @@ class QSK_EXPORT QskVariantAnimator : public QskAnimator
void setEndValue( const QVariant& );
QVariant endValue() const;
static bool maybeInterpolate( const QVariant&, const QVariant& );
static bool convertValues( QVariant&, QVariant& );
protected:
void setup() override;
void advance( qreal value ) override;