qskinny/src/controls/QskSkinnable.cpp

1175 lines
31 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskSkinnable.h"
2018-08-03 08:15:28 +02:00
#include "QskAnimationHint.h"
2017-07-21 18:21:34 +02:00
#include "QskAspect.h"
2018-08-03 08:15:28 +02:00
#include "QskColorFilter.h"
#include "QskControl.h"
#include "QskHintAnimator.h"
#include "QskMargins.h"
2017-07-21 18:21:34 +02:00
#include "QskSetup.h"
#include "QskSkin.h"
#include "QskSkinHintTable.h"
2017-07-21 18:21:34 +02:00
#include "QskSkinTransition.h"
2018-08-03 08:15:28 +02:00
#include "QskSkinlet.h"
2017-07-21 18:21:34 +02:00
2020-12-23 09:49:20 +01:00
#include "QskBoxShapeMetrics.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxBorderColors.h"
#include "QskGradient.h"
2018-07-19 14:10:48 +02:00
#include <qfont.h>
2017-07-21 18:21:34 +02:00
#define DEBUG_MAP 0
#define DEBUG_ANIMATOR 0
#define DEBUG_STATE 0
2017-07-21 18:21:34 +02:00
static inline bool qskIsControl( const QskSkinnable* skinnable )
{
2018-08-03 08:15:28 +02:00
#if QT_VERSION >= QT_VERSION_CHECK( 5, 7, 0 )
2017-07-21 18:21:34 +02:00
return skinnable->metaObject()->inherits( &QskControl::staticMetaObject );
#else
for ( auto mo = skinnable->metaObject();
mo != nullptr; mo = mo->superClass() )
{
if ( mo == &QskControl::staticMetaObject )
return true;
}
return false;
#endif
}
static inline bool qskCompareResolvedStates(
2020-12-21 16:06:58 +01:00
QskAspect& aspect1, QskAspect& aspect2, const QskSkinHintTable& table )
2017-07-21 18:21:34 +02:00
{
if ( !table.hasStates() )
return false;
const auto a1 = aspect1;
const auto a2 = aspect2;
Q_FOREVER
2017-07-21 18:21:34 +02:00
{
const auto s1 = aspect1.topState();
const auto s2 = aspect2.topState();
if ( s1 > s2 )
2017-07-21 18:21:34 +02:00
{
if ( table.hasHint( aspect1 ) )
return false;
2017-07-21 18:21:34 +02:00
aspect1.clearState( s1 );
2017-07-21 18:21:34 +02:00
}
else if ( s2 > s1 )
{
if ( table.hasHint( aspect2 ) )
return false;
aspect2.clearState( s2 );
}
else
2017-07-21 18:21:34 +02:00
{
if ( aspect1 == aspect2 )
2017-07-21 18:21:34 +02:00
{
if ( table.hasHint( aspect1 ) )
return true;
2017-07-21 18:21:34 +02:00
if ( s1 == 0 )
{
if ( aspect1.placement() == QskAspect::NoPlacement )
return true;
2017-07-21 18:21:34 +02:00
// clear the placement bits and restart with the initial state
aspect1 = a1;
aspect1.setPlacement( QskAspect::NoPlacement );
2017-07-21 18:21:34 +02:00
aspect2 = a2;
aspect2.setPlacement( QskAspect::NoPlacement );
}
}
else
{
if ( table.hasHint( aspect1 ) || table.hasHint( aspect2 ) )
return false;
}
2017-07-21 18:21:34 +02:00
aspect1.clearState( s1 );
aspect2.clearState( s2 );
2017-07-21 18:21:34 +02:00
}
}
}
2020-10-23 14:00:27 +02:00
static inline QVariant qskTypedNullValue( const QVariant& value )
{
#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
const auto vType = static_cast< QMetaType >( value.userType() );
#else
const auto vType = value.userType();
#endif
return QVariant( vType, nullptr );
}
static inline void qskSetFlag( QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
const QskAspect aspect, int flag )
{
skinnable->setSkinHint( aspect | QskAspect::Flag, QVariant( flag ) );
}
static inline int qskFlag( const QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status = nullptr )
{
2020-12-21 10:24:59 +01:00
return skinnable->effectiveSkinHint( aspect | QskAspect::Flag, status ).toInt();
}
static inline void qskSetMetric( QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const QVariant& metric )
{
skinnable->setSkinHint( aspect | QskAspect::Metric, metric );
}
template< typename T >
static inline void qskSetMetric( QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
QskAspect aspect, const T& metric )
{
qskSetMetric( skinnable, aspect, QVariant::fromValue( metric ) );
}
template< typename T >
static inline T qskMetric( const QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
QskAspect aspect, QskSkinHintStatus* status = nullptr )
{
2020-12-21 10:24:59 +01:00
return skinnable->effectiveSkinHint(
aspect | QskAspect::Metric, status ).value< T >();
}
static inline void qskSetColor( QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const QVariant& color )
{
skinnable->setSkinHint( aspect | QskAspect::Color, color );
}
template< typename T >
static inline void qskSetColor( QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const T& color )
{
qskSetColor( skinnable, aspect, QVariant::fromValue( color ) );
}
template< typename T >
static inline T qskColor( const QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
QskAspect aspect, QskSkinHintStatus* status = nullptr )
{
2020-12-21 10:24:59 +01:00
return skinnable->effectiveSkinHint(
aspect | QskAspect::Color, status ).value< T >();
}
2017-07-21 18:21:34 +02:00
class QskSkinnable::PrivateData
{
2018-08-03 08:15:28 +02:00
public:
PrivateData()
: skinlet( nullptr )
, skinState( QskAspect::NoState )
, hasLocalSkinlet( false )
2017-07-21 18:21:34 +02:00
{
}
~PrivateData()
{
2017-12-08 13:56:35 +01:00
if ( hasLocalSkinlet )
{
if ( skinlet && skinlet->isOwnedBySkinnable() )
delete skinlet;
}
2017-07-21 18:21:34 +02:00
}
QskSkinHintTable hintTable;
2017-07-21 18:21:34 +02:00
QskHintAnimatorTable animators;
const QskSkinlet* skinlet;
QskAspect::State skinState;
bool hasLocalSkinlet : 1;
};
2018-08-03 08:15:28 +02:00
QskSkinnable::QskSkinnable()
: m_data( new PrivateData() )
2017-07-21 18:21:34 +02:00
{
}
QskSkinnable::~QskSkinnable()
{
}
void QskSkinnable::setSkinlet( const QskSkinlet* skinlet )
{
if ( skinlet == m_data->skinlet )
{
if ( skinlet )
{
// now we don't depend on global skin changes anymore
m_data->hasLocalSkinlet = true;
}
return;
}
if ( m_data->skinlet && m_data->skinlet->isOwnedBySkinnable() )
delete m_data->skinlet;
m_data->skinlet = skinlet;
m_data->hasLocalSkinlet = ( skinlet != nullptr );
owningControl()->update();
}
const QskSkinlet* QskSkinnable::skinlet() const
{
return m_data->hasLocalSkinlet ? m_data->skinlet : nullptr;
}
const QskSkinlet* QskSkinnable::effectiveSkinlet() const
{
if ( m_data->skinlet == nullptr )
{
2020-12-23 09:41:29 +01:00
m_data->skinlet = qskSetup->skin()->skinlet( metaObject() );
2017-07-21 18:21:34 +02:00
m_data->hasLocalSkinlet = false;
}
return m_data->skinlet;
}
QskSkinHintTable& QskSkinnable::hintTable()
{
return m_data->hintTable;
}
const QskSkinHintTable& QskSkinnable::hintTable() const
{
return m_data->hintTable;
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setFlagHint( const QskAspect aspect, int flag )
2017-07-21 18:21:34 +02:00
{
qskSetFlag( this, aspect, flag );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
int QskSkinnable::flagHint( const QskAspect aspect ) const
2017-07-21 18:21:34 +02:00
{
2020-12-21 10:24:59 +01:00
return effectiveSkinHint( aspect ).toInt();
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setAlignmentHint( const QskAspect aspect, Qt::Alignment alignment )
{
qskSetFlag( this, aspect | QskAspect::Alignment, alignment );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetAlignmentHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
return resetFlagHint( aspect | QskAspect::Alignment );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setColor( const QskAspect aspect, const QColor& color )
2017-07-21 18:21:34 +02:00
{
qskSetColor( this, aspect, color );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setColor( const QskAspect aspect, Qt::GlobalColor color )
2017-07-21 18:21:34 +02:00
{
qskSetColor( this, aspect, QColor( color ) );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setColor( const QskAspect aspect, QRgb rgb )
2017-07-21 18:21:34 +02:00
{
qskSetColor( this, aspect, QColor::fromRgba( rgb ) );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
QColor QskSkinnable::color( const QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
return qskColor< QColor >( this, aspect, status );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setMetric( const QskAspect aspect, qreal metric )
2017-07-21 18:21:34 +02:00
{
qskSetMetric( this, aspect, metric );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
qreal QskSkinnable::metric( const QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
return qskMetric< qreal >( this, aspect, status );
2017-07-21 18:21:34 +02:00
}
void QskSkinnable::setStrutSizeHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, qreal width, qreal height )
{
qskSetMetric( this, aspect, QSizeF( width, height ) );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setStrutSizeHint( const QskAspect aspect, const QSizeF& strut )
{
qskSetMetric( this, aspect, strut );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetStrutSizeHint( const QskAspect aspect )
2020-12-17 08:53:00 +01:00
{
return resetMetric( aspect | QskAspect::StrutSize );
2020-12-17 08:53:00 +01:00
}
QSizeF QskSkinnable::strutSizeHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskMetric< QSizeF >( this, aspect | QskAspect::StrutSize, status );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setMarginHint( const QskAspect aspect, qreal margins )
{
qskSetMetric( this, aspect | QskAspect::Margin, QskMargins( margins ) );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setMarginHint( const QskAspect aspect, const QMarginsF& margins )
{
qskSetMetric( this, aspect | QskAspect::Margin, QskMargins( margins ) );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetMarginHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
return resetMetric( aspect | QskAspect::Margin );
2020-12-15 11:01:00 +01:00
}
QMarginsF QskSkinnable::marginHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskMetric< QskMargins >( this, aspect | QskAspect::Margin, status );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setPaddingHint( const QskAspect aspect, qreal padding )
2017-10-18 20:00:06 +02:00
{
qskSetMetric( this, aspect | QskAspect::Padding, QskMargins( padding ) );
2017-10-18 20:00:06 +02:00
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setPaddingHint( const QskAspect aspect, const QMarginsF& padding )
{
qskSetMetric( this, aspect | QskAspect::Padding, QskMargins( padding ) );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetPaddingHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
return resetMetric( aspect | QskAspect::Padding );
2020-12-15 11:01:00 +01:00
}
QMarginsF QskSkinnable::paddingHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskMetric< QskMargins >( this, aspect | QskAspect::Padding, status );
}
void QskSkinnable::setGradientHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const QskGradient& gradient )
{
qskSetColor( this, aspect, gradient );
}
QskGradient QskSkinnable::gradientHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskColor< QskGradient >( this, aspect, status );
}
void QskSkinnable::setBoxShapeHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const QskBoxShapeMetrics& shape )
{
qskSetMetric( this, aspect | QskAspect::Shape, shape );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetBoxShapeHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
return resetMetric( aspect | QskAspect::Shape );
2020-12-15 11:01:00 +01:00
}
QskBoxShapeMetrics QskSkinnable::boxShapeHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskMetric< QskBoxShapeMetrics >(
this, aspect | QskAspect::Shape, status );
}
2017-10-18 20:00:06 +02:00
void QskSkinnable::setBoxBorderMetricsHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const QskBoxBorderMetrics& border )
{
qskSetMetric( this, aspect | QskAspect::Border, border );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetBoxBorderMetricsHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
2020-12-21 10:24:59 +01:00
return resetMetric( aspect | QskAspect::Border );
2020-12-15 11:01:00 +01:00
}
2017-10-18 20:00:06 +02:00
QskBoxBorderMetrics QskSkinnable::boxBorderMetricsHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
return qskMetric< QskBoxBorderMetrics >(
this, aspect | QskAspect::Border, status );
}
2017-07-21 18:21:34 +02:00
2017-10-18 20:00:06 +02:00
void QskSkinnable::setBoxBorderColorsHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, const QskBoxBorderColors& colors )
{
qskSetColor( this, aspect | QskAspect::Border, colors );
}
2017-07-21 18:21:34 +02:00
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetBoxBorderColorsHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
return resetColor( aspect | QskAspect::Border );
2020-12-15 11:01:00 +01:00
}
2017-10-18 20:00:06 +02:00
QskBoxBorderColors QskSkinnable::boxBorderColorsHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskColor< QskBoxBorderColors >(
this, aspect | QskAspect::Border, status );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setSpacingHint( const QskAspect aspect, qreal spacing )
{
qskSetMetric( this, aspect | QskAspect::Spacing, spacing );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetSpacingHint( const QskAspect aspect )
2020-12-15 11:01:00 +01:00
{
return resetMetric( aspect | QskAspect::Spacing );
}
qreal QskSkinnable::spacingHint(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
{
return qskMetric< qreal >( this, aspect | QskAspect::Spacing, status );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setFontRole( const QskAspect aspect, int role )
2017-07-21 18:21:34 +02:00
{
qskSetFlag( this, aspect | QskAspect::FontRole, role );
2017-07-21 18:21:34 +02:00
}
2020-03-18 12:49:57 +01:00
int QskSkinnable::fontRole(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
return qskFlag( this, aspect | QskAspect::FontRole, status );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
QFont QskSkinnable::effectiveFont( const QskAspect aspect ) const
2017-07-21 18:21:34 +02:00
{
return effectiveSkin()->font( fontRole( aspect ) );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setGraphicRole( const QskAspect aspect, int role )
2017-07-21 18:21:34 +02:00
{
qskSetFlag( this, aspect | QskAspect::GraphicRole, role );
2017-07-21 18:21:34 +02:00
}
2020-03-18 12:49:57 +01:00
int QskSkinnable::graphicRole(
2020-12-21 16:06:58 +01:00
const QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
return qskFlag( this, aspect | QskAspect::GraphicRole, status );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
QskColorFilter QskSkinnable::effectiveGraphicFilter( QskAspect aspect ) const
2017-07-21 18:21:34 +02:00
{
aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) );
aspect.setPlacement( effectivePlacement() );
2017-07-21 18:21:34 +02:00
aspect = aspect | QskAspect::GraphicRole;
QskSkinHintStatus status;
const auto hint = storedHint( aspect | skinState(), &status );
2017-07-21 18:21:34 +02:00
if ( status.isValid() )
{
// we need to know about how the aspect gets resolved
// before checking for animators
aspect.setSubControl( status.aspect.subControl() );
}
if ( !aspect.isAnimator() )
{
auto v = animatedValue( aspect, nullptr );
2017-07-21 18:21:34 +02:00
if ( v.canConvert< QskColorFilter >() )
return v.value< QskColorFilter >();
if ( auto control = owningControl() )
{
v = QskSkinTransition::animatedGraphicFilter(
control->window(), hint.toInt() );
if ( v.canConvert< QskColorFilter >() )
{
/*
As it is hard to find out which controls depend
on the animated graphic filters we reschedule
our updates here.
*/
control->update();
return v.value< QskColorFilter >();
}
}
2017-07-21 18:21:34 +02:00
}
return effectiveSkin()->graphicFilter( hint.toInt() );
}
void QskSkinnable::setAnimationHint(
QskAspect aspect, QskAnimationHint animation )
2017-07-21 18:21:34 +02:00
{
aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) );
m_data->hintTable.setAnimation( aspect, animation );
2017-07-21 18:21:34 +02:00
}
QskAnimationHint QskSkinnable::animationHint(
2020-12-21 16:06:58 +01:00
QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
aspect.setAnimator( true );
2020-12-21 10:24:59 +01:00
return effectiveSkinHint( aspect, status ).value< QskAnimationHint >();
2017-07-21 18:21:34 +02:00
}
QskAnimationHint QskSkinnable::effectiveAnimation(
QskAspect::Type type, QskAspect::Subcontrol subControl,
2018-02-06 14:58:24 +01:00
QskAspect::State state, QskSkinHintStatus* status ) const
{
auto aspect = subControl | type | state;
aspect.setAnimator( true );
QskAnimationHint hint;
const auto a = m_data->hintTable.resolvedAnimator( aspect, hint );
if ( a.isAnimator() )
{
if ( status )
{
status->source = QskSkinHintStatus::Skinnable;
status->aspect = a;
}
return hint;
}
2019-04-19 17:04:36 +02:00
if ( auto skin = effectiveSkin() )
{
const auto a = skin->hintTable().resolvedAnimator( aspect, hint );
if ( a.isAnimator() )
{
if ( status )
{
status->source = QskSkinHintStatus::Skin;
status->aspect = a;
}
return hint;
}
}
if ( status )
{
status->source = QskSkinHintStatus::NoSource;
2020-12-21 16:06:58 +01:00
status->aspect = QskAspect();
}
return hint;
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::setSkinHint( QskAspect aspect, const QVariant& skinHint )
{
aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) );
m_data->hintTable.setHint( aspect, skinHint );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::resetSkinHint( QskAspect aspect )
2019-03-19 17:36:12 +01:00
{
aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) );
2019-12-14 16:40:18 +01:00
if ( !m_data->hintTable.hasHint( aspect ) )
return false;
2019-12-14 13:34:30 +01:00
2019-12-14 16:40:18 +01:00
/*
To be able to indicate, when the resolved value has changed
we retrieve the value before and after removing the hint from
the local table. An implementation with less lookups
should be possible, but as reset is a low frequently called
operation, we prefer to keep the implementation simple.
*/
2019-12-14 13:34:30 +01:00
2019-12-14 16:40:18 +01:00
auto a = aspect;
a.setPlacement( effectivePlacement() );
2020-12-23 09:28:17 +01:00
if ( !a.hasState() )
2020-12-20 16:10:24 +01:00
a.setState( skinState() );
2020-08-09 11:50:34 +02:00
2019-12-14 16:40:18 +01:00
const auto oldHint = storedHint( a );
m_data->hintTable.removeHint( aspect );
return oldHint != storedHint( a );
2019-03-19 17:36:12 +01:00
}
2020-12-21 10:24:59 +01:00
QVariant QskSkinnable::effectiveSkinHint(
2020-12-21 16:06:58 +01:00
QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) );
aspect.setPlacement( effectivePlacement() );
2017-07-21 18:21:34 +02:00
if ( aspect.isAnimator() )
2017-08-22 19:47:06 +02:00
return storedHint( aspect, status );
2017-07-21 18:21:34 +02:00
2020-12-20 16:10:24 +01:00
const auto v = animatedValue( aspect, status );
2017-07-21 18:21:34 +02:00
if ( v.isValid() )
return v;
2020-12-23 09:28:17 +01:00
if ( !aspect.hasState() )
2020-12-20 16:10:24 +01:00
aspect.setState( skinState() );
2017-07-21 18:21:34 +02:00
2017-08-22 19:47:06 +02:00
return storedHint( aspect, status );
2017-07-21 18:21:34 +02:00
}
2020-12-21 16:06:58 +01:00
QskSkinHintStatus QskSkinnable::hintStatus( QskAspect aspect ) const
2017-07-21 18:21:34 +02:00
{
QskSkinHintStatus status;
2020-12-21 10:24:59 +01:00
( void ) effectiveSkinHint( aspect, &status );
2017-07-21 18:21:34 +02:00
return status;
}
QVariant QskSkinnable::animatedValue(
2020-12-21 16:06:58 +01:00
QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
QVariant v;
2020-12-23 09:28:17 +01:00
if ( !aspect.hasState() )
2017-07-21 18:21:34 +02:00
{
/*
The local animators were invented to be stateless
and we never have an aspect with a state here.
But that might change ...
*/
v = m_data->animators.currentValue( aspect );
}
if ( !v.isValid() )
{
2018-08-03 08:15:28 +02:00
if ( QskSkinTransition::isRunning() &&
!m_data->hintTable.hasHint( aspect ) )
2017-07-21 18:21:34 +02:00
{
/*
Next we check for values from the skin. Those
animators are usually from global skin/color changes
and are state aware
*/
if ( const auto control = owningControl() )
2017-07-21 18:21:34 +02:00
{
if ( aspect.state() == QskAspect::NoState )
aspect = aspect | skinState();
2017-07-21 18:21:34 +02:00
const auto a = aspect;
2019-04-19 17:04:36 +02:00
Q_FOREVER
{
v = QskSkinTransition::animatedHint( control->window(), aspect );
2019-04-19 17:04:36 +02:00
if ( !v.isValid() )
{
if ( const auto topState = aspect.topState() )
{
aspect.clearState( aspect.topState() );
continue;
}
if ( aspect.placement() )
{
// clear the placement bits and restart
aspect = a;
aspect.setPlacement( QskAspect::NoPlacement );
continue;
}
2019-04-19 17:04:36 +02:00
}
break;
}
2017-07-21 18:21:34 +02:00
}
}
}
if ( status && v.isValid() )
{
status->source = QskSkinHintStatus::Animator;
status->aspect = aspect;
}
return v;
}
2017-08-22 19:47:06 +02:00
const QVariant& QskSkinnable::storedHint(
2020-12-21 16:06:58 +01:00
QskAspect aspect, QskSkinHintStatus* status ) const
2017-07-21 18:21:34 +02:00
{
const auto skin = effectiveSkin();
// clearing all state bits not being handled from the skin
aspect.clearState( ~skin->stateMask() );
2020-08-09 11:50:34 +02:00
2020-12-21 16:06:58 +01:00
QskAspect resolvedAspect;
const auto& localTable = m_data->hintTable;
if ( localTable.hasHints() )
{
auto a = aspect;
if ( !localTable.hasStates() )
{
// we don't need to clear the state bits stepwise
a.clearStates();
}
if ( const QVariant* value = localTable.resolvedHint( a, &resolvedAspect ) )
{
if ( status )
{
status->source = QskSkinHintStatus::Skinnable;
status->aspect = resolvedAspect;
}
return *value;
}
}
// next we try the hints from the skin
const auto& skinTable = skin->hintTable();
if ( skinTable.hasHints() )
{
auto a = aspect;
const QVariant* value = skinTable.resolvedHint( a, &resolvedAspect );
if ( value )
{
if ( status )
{
status->source = QskSkinHintStatus::Skin;
status->aspect = resolvedAspect;
}
return *value;
}
if ( aspect.subControl() != QskAspect::Control )
{
2019-12-14 13:34:30 +01:00
// trying to resolve something from the skin default settings
aspect.setSubControl( QskAspect::Control );
aspect.clearStates();
value = skinTable.resolvedHint( aspect, &resolvedAspect );
if ( value )
{
if ( status )
{
status->source = QskSkinHintStatus::Skin;
status->aspect = resolvedAspect;
}
return *value;
}
}
}
if ( status )
{
status->source = QskSkinHintStatus::NoSource;
2020-12-21 16:06:58 +01:00
status->aspect = QskAspect();
}
static QVariant hintInvalid;
return hintInvalid;
2017-07-21 18:21:34 +02:00
}
QskAspect::State QskSkinnable::skinState() const
{
return m_data->skinState;
}
const char* QskSkinnable::skinStateAsPrintable() const
{
return skinStateAsPrintable( skinState() );
}
const char* QskSkinnable::skinStateAsPrintable( QskAspect::State state ) const
{
QString tmp;
QDebug debug( &tmp );
qskDebugState( debug, metaObject(), state );
// we should find a better way
2018-08-03 08:15:28 +02:00
static QByteArray bytes[ 10 ];
2017-07-21 18:21:34 +02:00
static int counter = 0;
counter = ( counter + 1 ) % 10;
2018-08-03 08:15:28 +02:00
bytes[ counter ] = tmp.toUtf8();
return bytes[ counter ].constData();
2017-07-21 18:21:34 +02:00
}
2020-12-17 16:44:54 +01:00
static inline QskMargins qskEffectivePadding( const QskSkinnable* skinnable,
2020-12-21 16:06:58 +01:00
QskAspect aspect, const QSizeF& size, bool inner )
2017-07-21 18:21:34 +02:00
{
const auto shape = skinnable->boxShapeHint( aspect ).toAbsolute( size );
const auto borderMetrics = skinnable->boxBorderMetricsHint( aspect );
2017-07-21 18:21:34 +02:00
const qreal left = qMax( shape.radius( Qt::TopLeftCorner ).width(),
shape.radius( Qt::BottomLeftCorner ).width() );
2017-10-20 13:09:30 +02:00
const qreal top = qMax( shape.radius( Qt::TopLeftCorner ).height(),
shape.radius( Qt::TopRightCorner ).height() );
2017-10-20 13:09:30 +02:00
const qreal right = qMax( shape.radius( Qt::TopRightCorner ).width(),
shape.radius( Qt::BottomRightCorner ).width() );
2017-10-20 13:09:30 +02:00
const qreal bottom = qMax( shape.radius( Qt::BottomLeftCorner ).height(),
shape.radius( Qt::BottomRightCorner ).height() );
2020-12-17 16:44:54 +01:00
QskMargins padding( left, top, right, bottom );
2017-10-20 13:09:30 +02:00
// half of the border goes to the inner side
const auto borderMargins = borderMetrics.toAbsolute( size ).widths() * 0.5;
if ( inner )
{
padding -= borderMargins;
}
else
{
// not correct, but to get things started. TODO ...
padding += borderMargins;
}
// sin 45° ceiled : 0.70710678;
padding *= 1.0 - 0.70710678;
const auto paddingHint = skinnable->paddingHint( aspect );
2017-07-21 18:21:34 +02:00
2020-12-17 16:44:54 +01:00
return QskMargins(
2017-10-20 13:09:30 +02:00
qMax( padding.left(), paddingHint.left() ),
qMax( padding.top(), paddingHint.top() ),
qMax( padding.right(), paddingHint.right() ),
qMax( padding.bottom(), paddingHint.bottom() )
2020-08-09 11:50:34 +02:00
);
2017-07-21 18:21:34 +02:00
}
QMarginsF QskSkinnable::innerPadding(
2020-12-21 16:06:58 +01:00
QskAspect aspect, const QSizeF& outerBoxSize ) const
{
return qskEffectivePadding( this, aspect, outerBoxSize, true );
}
2017-07-21 18:21:34 +02:00
QSizeF QskSkinnable::innerBoxSize(
2020-12-21 16:06:58 +01:00
QskAspect aspect, const QSizeF& outerBoxSize ) const
2017-07-21 18:21:34 +02:00
{
2020-12-17 16:44:54 +01:00
const auto pd = qskEffectivePadding( this, aspect, outerBoxSize, true );
2017-07-21 18:21:34 +02:00
2020-12-17 16:44:54 +01:00
return QSizeF( outerBoxSize.width() - pd.width(),
outerBoxSize.height() - pd.height() );
2017-07-21 18:21:34 +02:00
}
QRectF QskSkinnable::innerBox(
2020-12-21 16:06:58 +01:00
QskAspect aspect, const QRectF& outerBox ) const
2017-07-21 18:21:34 +02:00
{
2020-12-17 16:44:54 +01:00
const auto pd = qskEffectivePadding( this, aspect, outerBox.size(), true );
return outerBox.marginsRemoved( pd );
2017-07-21 18:21:34 +02:00
}
QSizeF QskSkinnable::outerBoxSize(
2020-12-21 16:06:58 +01:00
QskAspect aspect, const QSizeF& innerBoxSize ) const
2017-07-21 18:21:34 +02:00
{
2020-12-17 16:44:54 +01:00
const auto pd = qskEffectivePadding( this, aspect, innerBoxSize, false );
2017-07-21 18:21:34 +02:00
2020-12-17 16:44:54 +01:00
// since Qt 5.14 we would have QSizeF::grownBy !
return QSizeF( innerBoxSize.width() + pd.width(),
innerBoxSize.height() + pd.height() );
2017-07-21 18:21:34 +02:00
}
QRectF QskSkinnable::outerBox(
2020-12-21 16:06:58 +01:00
QskAspect aspect, const QRectF& innerBox ) const
2017-07-21 18:21:34 +02:00
{
2020-08-09 10:45:48 +02:00
const auto m = qskEffectivePadding( this, aspect, innerBox.size(), false );
2017-07-21 18:21:34 +02:00
return innerBox.marginsAdded( m );
}
2020-12-21 16:06:58 +01:00
bool QskSkinnable::isTransitionAccepted( QskAspect aspect ) const
{
Q_UNUSED( aspect )
/*
Usually we only need smooth transitions, when state changes
happen while the skinnable is visible. There are few exceptions
like QskPopup::Closed, that is used to slide/fade in.
*/
if ( auto control = owningControl() )
return control->isInitiallyPainted();
return false;
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::startTransition( QskAspect aspect,
2017-07-21 18:21:34 +02:00
QskAnimationHint animationHint, QVariant from, QVariant to )
{
aspect.setSubControl( effectiveSubcontrol( aspect.subControl() ) );
startHintTransition( aspect, animationHint, from, to );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::startHintTransition( QskAspect aspect,
QskAnimationHint animationHint, QVariant from, QVariant to )
2017-07-21 18:21:34 +02:00
{
if ( animationHint.duration <= 0 || ( from == to ) )
return;
2020-08-09 10:45:48 +02:00
auto control = this->owningControl();
if ( control->window() == nullptr || !isTransitionAccepted( aspect ) )
2017-07-21 18:21:34 +02:00
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.
*/
2017-09-05 12:48:58 +02:00
if ( !from.isValid() )
2017-07-21 18:21:34 +02:00
{
2020-10-23 14:00:27 +02:00
from = qskTypedNullValue( to );
2017-07-21 18:21:34 +02:00
}
2017-09-05 12:48:58 +02:00
else if ( !to.isValid() )
2017-07-21 18:21:34 +02:00
{
2020-10-23 14:00:27 +02:00
to = qskTypedNullValue( from );
2017-09-05 12:48:58 +02:00
}
else if ( from.userType() != to.userType() )
2017-09-05 12:48:58 +02:00
{
return;
2017-07-21 18:21:34 +02:00
}
2018-08-03 08:15:28 +02:00
if ( aspect.flagPrimitive() == QskAspect::GraphicRole )
2017-07-21 18:21:34 +02:00
{
const auto skin = effectiveSkin();
from.setValue( skin->graphicFilter( from.toInt() ) );
to.setValue( skin->graphicFilter( to.toInt() ) );
}
aspect.clearStates();
aspect.setAnimator( false );
aspect.setPlacement( effectivePlacement() );
2017-07-21 18:21:34 +02:00
#if DEBUG_ANIMATOR
2018-02-06 14:58:24 +01:00
qDebug() << aspect << animationHint.duration;
#endif
2017-07-21 18:21:34 +02:00
auto animator = m_data->animators.animator( aspect );
if ( animator && animator->isRunning() )
from = animator->currentValue();
2017-07-21 18:21:34 +02:00
m_data->animators.start( control, aspect, animationHint, from, to );
2017-07-21 18:21:34 +02:00
}
void QskSkinnable::setSkinStateFlag( QskAspect::State stateFlag, bool on )
2017-07-21 18:21:34 +02:00
{
2018-08-03 08:15:28 +02:00
const auto newState = on
? ( m_data->skinState | stateFlag )
: ( m_data->skinState & ~stateFlag );
2017-07-21 18:21:34 +02:00
setSkinState( newState );
}
2020-07-27 07:26:38 +02:00
void QskSkinnable::setSkinState( QskAspect::State newState, bool animated )
{
2017-07-21 18:21:34 +02:00
if ( m_data->skinState == newState )
return;
auto control = owningControl();
2017-09-05 12:48:58 +02:00
#if DEBUG_STATE
qDebug() << control->className() << ":"
2017-09-05 12:48:58 +02:00
<< skinStateAsPrintable( m_data->skinState ) << "->"
<< skinStateAsPrintable( newState );
#endif
const auto skin = effectiveSkin();
if ( skin )
{
const auto mask = skin->stateMask();
if ( ( newState & mask ) == ( m_data->skinState & mask ) )
{
// the modified bits are not handled by the skin
m_data->skinState = newState;
return;
}
}
2020-12-21 16:06:58 +01:00
if ( control->window() && animated && isTransitionAccepted( QskAspect() ) )
2017-07-21 18:21:34 +02:00
{
const auto placement = effectivePlacement();
2017-07-21 18:21:34 +02:00
2017-10-30 12:06:19 +01:00
const auto subControls = control->subControls();
for ( const auto subControl : subControls )
{
auto aspect = subControl | placement;
const auto& skinTable = skin->hintTable();
2017-07-21 18:21:34 +02:00
2020-12-18 13:09:22 +01:00
for ( uint i = 0; i < QskAspect::typeCount; i++ )
2017-07-21 18:21:34 +02:00
{
const auto type = static_cast< QskAspect::Type >( i );
2018-02-06 14:58:24 +01:00
const auto hint = effectiveAnimation( type, subControl, newState );
2017-07-21 18:21:34 +02:00
if ( hint.duration > 0 )
{
/*
Starting an animator for all primitives,
that differ between the states
*/
2020-12-18 13:09:22 +01:00
const auto primitiveCount = QskAspect::primitiveCount( type );
for ( uint primitive = 0; primitive < primitiveCount; primitive++ )
{
aspect.setPrimitive( type, primitive );
auto a1 = aspect | m_data->skinState;
auto a2 = aspect | newState;
bool doTransition = true;
if ( !m_data->hintTable.hasStates() )
{
/*
The hints are found by stripping the state bits one by
one until a lookup into the hint table is successful.
So for deciding whether two aspects lead to the same hint
we can stop as soon as the aspects have the same state bits.
This way we can reduce the number of lookups significantly
for skinnables with many state bits.
*/
doTransition = !qskCompareResolvedStates( a1, a2, skinTable );
}
if ( doTransition )
{
2020-12-21 16:06:58 +01:00
startHintTransition( aspect, hint,
storedHint( a1 ), storedHint( a2 ) );
}
}
2017-07-21 18:21:34 +02:00
}
}
}
}
m_data->skinState = newState;
if ( control->flags() & QQuickItem::ItemHasContents )
control->update();
2017-07-21 18:21:34 +02:00
}
QskSkin* QskSkinnable::effectiveSkin() const
{
QskSkin* skin = nullptr;
if ( m_data->skinlet )
skin = m_data->skinlet->skin();
2017-07-21 18:21:34 +02:00
return skin ? skin : qskSetup->skin();
}
2020-12-20 16:10:24 +01:00
QskAspect::Placement QskSkinnable::effectivePlacement() const
{
return QskAspect::NoPlacement;
}
2017-07-21 18:21:34 +02:00
void QskSkinnable::updateNode( QSGNode* parentNode )
{
effectiveSkinlet()->updateNode( this, parentNode );
}
QskAspect::Subcontrol QskSkinnable::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
{
// derived classes might want to redirect a sub-control
return subControl;
}
QskControl* QskSkinnable::controlCast()
{
return qskIsControl( this )
? static_cast< QskControl* >( this ) : nullptr;
}
const QskControl* QskSkinnable::controlCast() const
{
return qskIsControl( this )
? static_cast< const QskControl* >( this ) : nullptr;
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::debug( QDebug debug, QskAspect aspect ) const
2017-07-21 18:21:34 +02:00
{
qskDebugAspect( debug, metaObject(), aspect );
}
void QskSkinnable::debug( QDebug debug, QskAspect::State state ) const
{
qskDebugState( debug, metaObject(), state );
}
2020-12-21 16:06:58 +01:00
void QskSkinnable::debug( QskAspect aspect ) const
2017-07-21 18:21:34 +02:00
{
qskDebugAspect( qDebug(), metaObject(), aspect );
}
void QskSkinnable::debug( QskAspect::State state ) const
{
qskDebugState( qDebug(), metaObject(), state );
}
2020-09-28 09:04:25 +02:00
#ifndef QT_NO_DEBUG_STREAM
#include <qdebug.h>
QDebug operator<<( QDebug debug, const QskSkinHintStatus& status )
{
QDebugStateSaver saver( debug );
debug.nospace();
switch( status.source )
{
case QskSkinHintStatus::Skinnable:
debug << "Skinnable";
break;
case QskSkinHintStatus::Skin:
debug << "Skin";
break;
case QskSkinHintStatus::Animator:
debug << "Animator";
break;
default:
debug << "None";
break;
}
debug << ": " << status.aspect;
return debug;
}
#endif