QskPageIndicator improved

This commit is contained in:
Uwe Rathmann 2022-01-04 13:58:34 +01:00
parent 8ab578ff19
commit 1feccd942a
7 changed files with 181 additions and 203 deletions

View File

@ -184,13 +184,12 @@ StackLayoutPage::StackLayoutPage( QQuickItem* parent )
auto progressBar = new ProgressBar(); auto progressBar = new ProgressBar();
progressBar->setMargins( QMarginsF( 0, 0, 10, 0 ) ); progressBar->setMargins( QMarginsF( 0, 0, 10, 0 ) );
addItem( buttonBox ); addItem( buttonBox );
addItem( pageIndicator ); addItem( pageIndicator );
addItem( box ); addItem( box );
addItem( progressBar, Qt::AlignRight ); addItem( progressBar, Qt::AlignRight );
connect( box, &QskStackBox::currentIndexChanged, connect( box, &QskStackBox::transientIndexChanged,
pageIndicator, &QskPageIndicator::setCurrentIndex ); pageIndicator, &QskPageIndicator::setCurrentIndex );
connect( box, &StackBox::transitionStarted, connect( box, &StackBox::transitionStarted,

View File

@ -281,27 +281,19 @@ void Editor::setupPageIndicator()
{ {
using Q = QskPageIndicator; using Q = QskPageIndicator;
for ( auto subControl : { Q::Bullet, Q::Highlighted } ) const auto extent = qskDpiScaled( 9 );
{ setStrutSize( Q::Bullet, extent, extent );
const auto extent = qskDpiScaled( 10 );
setStrutSize( subControl, extent, extent );
// circles, without border // circles, without border
setBoxShape( subControl, 100, Qt::RelativeSize ); setBoxShape( Q::Bullet, 100, Qt::RelativeSize );
setBoxBorderMetrics( subControl, 0 ); setBoxBorderMetrics( Q::Bullet, 0 );
const QColor color = ( subControl == Q::Bullet ) setGradient( Q::Bullet, m_pal.lighter150 );
? m_pal.lighter150 : m_pal.accentColor; setGradient( Q::Bullet | Q::Selected, m_pal.accentColor );
setGradient( subControl, color ); setSpacing( Q::Panel, 5 );
setBoxBorderColors( subControl, color ); setPadding( Q::Panel, 0 );
} setGradient( Q::Panel, QskGradient() ); // invisible
// no visible background panel
setBoxBorderMetrics( Q::Panel, 0 );
setGradient( Q::Panel, QskGradient() );
setSpacing( Q::Panel, 3 );
} }
void Editor::setupPushButton() void Editor::setupPushButton()

View File

@ -441,27 +441,24 @@ void Editor::setupSeparator()
void Editor::setupPageIndicator() void Editor::setupPageIndicator()
{ {
using A = QskAspect;
using Q = QskPageIndicator; using Q = QskPageIndicator;
for ( auto subControl : { Q::Bullet, Q::Highlighted } ) const auto extent = qskDpiScaled( 8 );
{ setStrutSize( Q::Bullet, extent, extent );
const auto extent = qskDpiScaled( 12 );
setStrutSize( subControl, extent, extent );
setBoxBorderMetrics( subControl, 50, Qt::RelativeSize ); // circles, without border
setBoxShape( subControl, 100, Qt::RelativeSize ); setBoxShape( Q::Bullet, 100, Qt::RelativeSize );
} setBoxBorderMetrics( Q::Bullet, 0 );
setGradient( Q::Bullet, m_pal.darker150 ); setGradient( Q::Bullet, m_pal.darker150 );
setGradient( Q::Highlighted, m_pal.lighter150 ); setMargin( Q::Bullet, qskDpiScaled( 1 ) );
// no visible background panel setGradient( Q::Bullet | Q::Selected, m_pal.lighter150 );
setBoxBorderMetrics( Q::Panel, 0 ); setMargin( Q::Bullet | Q::Selected, 0 );
setBoxShape( Q::Panel, 2 );
setGradient( Q::Panel, QskGradient() );
setMetric( Q::Panel | A::Spacing, 3 ); setSpacing( Q::Panel, 3 );
setPadding( Q::Panel, 0 );
setGradient( Q::Panel, QskGradient() ); // invisible
} }
void Editor::setupPushButton() void Editor::setupPushButton()

View File

@ -8,20 +8,24 @@
QSK_SUBCONTROL( QskPageIndicator, Panel ) QSK_SUBCONTROL( QskPageIndicator, Panel )
QSK_SUBCONTROL( QskPageIndicator, Bullet ) QSK_SUBCONTROL( QskPageIndicator, Bullet )
QSK_SUBCONTROL( QskPageIndicator, Highlighted )
QSK_SYSTEM_STATE( QskPageIndicator, Selected, QskAspect::FirstSystemState << 1 )
class QskPageIndicator::PrivateData class QskPageIndicator::PrivateData
{ {
public: public:
PrivateData( int count ) PrivateData( int count )
: count( count ) : count( count )
, currentIndex( -1 ) , interactive( false )
, orientation( Qt::Horizontal ) , orientation( Qt::Horizontal )
{ {
} }
qreal currentIndex = -1;
int count; int count;
qreal currentIndex;
bool interactive : 1;
Qt::Orientation orientation : 2; Qt::Orientation orientation : 2;
}; };
@ -70,6 +74,25 @@ void QskPageIndicator::setOrientation( Qt::Orientation orientation )
} }
} }
bool QskPageIndicator::isInteractive() const
{
return m_data->interactive;
}
void QskPageIndicator::setInteractive( bool on )
{
if ( on != m_data->interactive )
{
m_data->interactive = on;
// this flag might have an impact on its representation
resetImplicitSize();
update();
Q_EMIT interactiveChanged( on );
}
}
void QskPageIndicator::setCount( int count ) void QskPageIndicator::setCount( int count )
{ {
if ( count != m_data->count ) if ( count != m_data->count )
@ -97,4 +120,20 @@ void QskPageIndicator::setCurrentIndex( qreal index )
} }
} }
qreal QskPageIndicator::valueRatioAt( int index ) const
{
if ( m_data->currentIndex >= 0.0 && index >= 0 )
{
qreal pos = m_data->currentIndex;
if ( index == 0 && pos > m_data->count - 1 )
pos -= m_data->count;
const qreal diff = 1.0 - std::abs( pos - index );
return std::max( diff, 0.0 );
}
return 0.0;
}
#include "moc_QskPageIndicator.cpp" #include "moc_QskPageIndicator.cpp"

View File

@ -18,13 +18,17 @@ class QSK_EXPORT QskPageIndicator : public QskControl
Q_PROPERTY( qreal currentIndex READ currentIndex Q_PROPERTY( qreal currentIndex READ currentIndex
WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL ) WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL )
Q_PROPERTY( bool interactive READ isInteractive
WRITE setInteractive NOTIFY interactiveChanged FINAL )
Q_PROPERTY( Qt::Orientation orientation READ orientation Q_PROPERTY( Qt::Orientation orientation READ orientation
WRITE setOrientation NOTIFY orientationChanged FINAL ) WRITE setOrientation NOTIFY orientationChanged FINAL )
using Inherited = QskControl; using Inherited = QskControl;
public: public:
QSK_SUBCONTROLS( Panel, Bullet, Highlighted ) QSK_SUBCONTROLS( Panel, Bullet )
QSK_STATES( Selected )
QskPageIndicator( QQuickItem* parent = nullptr ); QskPageIndicator( QQuickItem* parent = nullptr );
QskPageIndicator( int count, QQuickItem* parent = nullptr ); QskPageIndicator( int count, QQuickItem* parent = nullptr );
@ -32,16 +36,21 @@ class QSK_EXPORT QskPageIndicator : public QskControl
~QskPageIndicator() override; ~QskPageIndicator() override;
int count() const; int count() const;
qreal currentIndex() const; qreal currentIndex() const;
Qt::Orientation orientation() const; Qt::Orientation orientation() const;
void setOrientation( Qt::Orientation ); void setOrientation( Qt::Orientation );
bool isInteractive() const;
void setInteractive( bool );
qreal valueRatioAt( int index ) const;
Q_SIGNALS: Q_SIGNALS:
void countChanged( int ); void countChanged( int );
void currentIndexChanged( qreal ); void currentIndexChanged( qreal );
void orientationChanged( Qt::Orientation ); void orientationChanged( Qt::Orientation );
void interactiveChanged( bool );
public Q_SLOTS: public Q_SLOTS:
void setCount( int count ); void setCount( int count );

View File

@ -8,6 +8,50 @@
#include "QskBoxNode.h" #include "QskBoxNode.h"
#include "QskSGNode.h" #include "QskSGNode.h"
#include "QskFunctions.h"
static inline int qskCurrentIndex( const QskPageIndicator* indicator )
{
int index = qRound( indicator->currentIndex() );
if ( index >= indicator->count() )
index = 0;
return index;
}
static QRectF qskBulletRect( const QskPageIndicator* indicator,
const QRectF& rect, int index )
{
using Q = QskPageIndicator;
/*
The bullets might have different sizes, but as a pager indicator
usually does not have many bullets we can simply iterate
*/
const qreal spacing = indicator->spacingHint( Q::Panel );
const auto size = indicator->strutSizeHint( Q::Bullet );
qreal x = rect.x();
qreal y = rect.y();
if ( indicator->orientation() == Qt::Horizontal )
{
for ( int i = 0; i < index; i++ )
x += size.width() + spacing;
y += 0.5 * ( rect.height() - size.height() );
}
else
{
for ( int i = 0; i < index; i++ )
y += size.height() + spacing;
x += 0.5 * ( rect.width() - size.width() );
}
return QRectF( x, y, size.width(), size.height() );
}
QskPageIndicatorSkinlet::QskPageIndicatorSkinlet( QskSkin* skin ) QskPageIndicatorSkinlet::QskPageIndicatorSkinlet( QskSkin* skin )
: QskSkinlet( skin ) : QskSkinlet( skin )
@ -23,9 +67,7 @@ QRectF QskPageIndicatorSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{ {
if ( subControl == QskPageIndicator::Panel ) if ( subControl == QskPageIndicator::Panel )
{
return contentsRect; return contentsRect;
}
return Inherited::subControlRect( skinnable, contentsRect, subControl ); return Inherited::subControlRect( skinnable, contentsRect, subControl );
} }
@ -33,157 +75,75 @@ QRectF QskPageIndicatorSkinlet::subControlRect( const QskSkinnable* skinnable,
QSGNode* QskPageIndicatorSkinlet::updateSubNode( QSGNode* QskPageIndicatorSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{ {
const auto indicator = static_cast< const QskPageIndicator* >( skinnable ); using Q = QskPageIndicator;
switch ( nodeRole ) switch ( nodeRole )
{ {
case PanelRole: case PanelRole:
{ return updateBoxNode( skinnable, node, Q::Panel );
return updateBoxNode( indicator, node, QskPageIndicator::Panel );
}
case BulletsRole: case BulletsRole:
{ return updateSeriesNode( skinnable, Q::Bullet, node );
return updateBulletsNode( indicator, node );
}
} }
return Inherited::updateSubNode( skinnable, nodeRole, node ); return Inherited::updateSubNode( skinnable, nodeRole, node );
} }
QRectF QskPageIndicatorSkinlet::bulletRect( int QskPageIndicatorSkinlet::sampleCount(
const QskPageIndicator* indicator, const QRectF& rect, int index ) const const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const
{ {
using Q = QskPageIndicator; using Q = QskPageIndicator;
const auto szNormal = indicator->strutSizeHint( Q::Bullet ); if ( subControl == Q::Bullet )
const auto szHighlighted = indicator->strutSizeHint( Q::Highlighted );
const qreal wNormal = szNormal.width();
const qreal wHighlighted = szHighlighted.width();
const qreal hNormal = szNormal.height();
const qreal hHighlighted = szHighlighted.height();
const auto currentIndex = indicator->currentIndex();
// scale bullet size if we are in between a transition:
qreal indexDiff = qAbs( currentIndex - index );
if ( indexDiff > ( indicator->count() - 1 ) )
indexDiff = ( indicator->count() - currentIndex ); // wrapping
const qreal w0 = ( indexDiff < 1 ) ?
( 1 - indexDiff ) * wHighlighted + indexDiff * wNormal : wNormal;
const qreal h0 = ( indexDiff < 1 ) ?
( 1 - indexDiff ) * hHighlighted + indexDiff * hNormal : hNormal;
const qreal spacing = indicator->spacingHint( Q::Panel );
const bool horizontal = ( indicator->orientation() == Qt::Horizontal );
qreal w, h;
if ( horizontal )
{ {
w = ( indicator->count() - 1 ) * ( wNormal + spacing ) + wHighlighted; const auto indicator = static_cast< const QskPageIndicator* >( skinnable );
h = rect.height(); return indicator->count();
}
else
{
w = rect.width();
h = ( indicator->count() - 1 ) * ( hNormal + spacing ) + hHighlighted;
} }
QRectF r( 0, 0, w, h ); return Inherited::sampleCount( skinnable, subControl );
r.moveCenter( rect.center() );
qreal x2, y2;
{
const qreal w = ( index > currentIndex ) ? wHighlighted : wNormal;
const qreal h = ( index > currentIndex ) ? hHighlighted : hNormal;
if ( indexDiff < 1 && index >= currentIndex )
{
// scrolling from or to this bullet:
x2 = wNormal + qAbs( wHighlighted - wNormal ) * indexDiff;
y2 = hNormal + qAbs( hHighlighted - hNormal ) * indexDiff;
}
else if ( ( currentIndex > ( indicator->count() - 1 ) &&
index > ( currentIndex - indicator->count() + 1 ) ) )
{
// wrapping case:
qreal wrappingDiff = indexDiff;
while ( wrappingDiff > 1 )
wrappingDiff -= 1;
x2 = wNormal + qAbs( wHighlighted - wNormal ) * wrappingDiff;
y2 = hNormal + qAbs( hHighlighted - hNormal ) * wrappingDiff;
}
else
{
x2 = w;
y2 = h;
}
}
const qreal x = r.left() + x2 + spacing + ( index - 1 ) * ( wNormal + spacing );
const qreal y = r.top() + y2 + spacing + ( index - 1 ) * ( hNormal + spacing );
qreal adjust = ( currentIndex == index )
? ( wNormal - wHighlighted ) : ( wHighlighted - wNormal );
adjust = 0.5 * qMax( 0.0, adjust );
if ( indexDiff < 1 )
adjust *= indexDiff;
QRectF bulletRect( 0.0, 0.0, w0, h0 );
if ( horizontal )
bulletRect.moveTo( x, r.top() + adjust );
else
bulletRect.moveTo( r.left() + adjust, y );
return bulletRect;
} }
QSGNode* QskPageIndicatorSkinlet::updateBulletsNode( QRectF QskPageIndicatorSkinlet::sampleRect( const QskSkinnable* skinnable,
const QskPageIndicator* indicator, QSGNode* node ) const const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const
{ {
if ( indicator->count() == 0 )
return nullptr;
if ( node == nullptr )
node = new QSGNode();
const auto rect = indicator->subControlContentsRect( QskPageIndicator::Panel );
// index of the highlighted bullet
int currentBullet = qRound( indicator->currentIndex() );
if ( currentBullet >= indicator->count() )
currentBullet = 0;
auto bulletNode = node->firstChild();
for ( int i = 0; i < indicator->count(); i++ )
{
using Q = QskPageIndicator; using Q = QskPageIndicator;
if ( i > 0 ) if ( subControl == Q::Bullet )
bulletNode = bulletNode->nextSibling(); {
const auto indicator = static_cast< const QskPageIndicator* >( skinnable );
if ( bulletNode == nullptr ) const auto rect = indicator->subControlContentsRect( Q::Panel );
bulletNode = new QskBoxNode(); return qskBulletRect( indicator, rect, index );
updateBoxNode( indicator, bulletNode, bulletRect( indicator, rect, i ),
( i == currentBullet ) ? Q::Highlighted : Q::Bullet );
if ( bulletNode->parent() != node )
node->appendChildNode( bulletNode );
} }
// if count has decreased we need to remove superfluous nodes return Inherited::sampleRect( skinnable, contentsRect, subControl, index );
QskSGNode::removeAllChildNodesAfter( node, bulletNode ); }
return node; int QskPageIndicatorSkinlet::sampleIndexAt(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, const QPointF& pos ) const
{
// TODO ...
return Inherited::sampleIndexAt( skinnable, contentsRect, subControl, pos );
}
QSGNode* QskPageIndicatorSkinlet::updateSampleNode( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
{
using Q = QskPageIndicator;
if ( subControl == Q::Bullet )
{
auto indicator = static_cast< const QskPageIndicator* >( skinnable );
const auto rect = sampleRect( indicator, indicator->contentsRect(), Q::Bullet, index );
const auto ratio = indicator->valueRatioAt( index );
return QskSkinlet::updateInterpolatedBoxNode( skinnable, node,
rect, Q::Bullet, Q::Bullet | Q::Selected, ratio );
}
return nullptr;
} }
QSizeF QskPageIndicatorSkinlet::sizeHint( const QskSkinnable* skinnable, QSizeF QskPageIndicatorSkinlet::sizeHint( const QskSkinnable* skinnable,
@ -196,44 +156,21 @@ QSizeF QskPageIndicatorSkinlet::sizeHint( const QskSkinnable* skinnable,
const auto indicator = static_cast< const QskPageIndicator* >( skinnable ); const auto indicator = static_cast< const QskPageIndicator* >( skinnable );
const auto bulletSize = indicator->strutSizeHint( Q::Bullet ); QSizeF size( 0.0, 0.0 );
const auto maxSize = bulletSize.expandedTo(
indicator->strutSizeHint( Q::Highlighted ) );
const qreal spacing = indicator->spacingHint( Q::Panel );
const int n = indicator->count(); const int n = indicator->count();
qreal w = 0; if ( n > 0 )
qreal h = 0; {
size = indicator->strutSizeHint( Q::Bullet );
const qreal spacing = indicator->spacingHint( Q::Panel );
if ( indicator->orientation() == Qt::Horizontal ) if ( indicator->orientation() == Qt::Horizontal )
{ size.rwidth() += ( n - 1 ) * ( size.width() + spacing );
if ( n > 0 )
{
w += maxSize.width();
if ( n > 1 )
w += ( n - 1 ) * ( bulletSize.width() + spacing );
}
h = maxSize.height();
}
else else
{ size.rheight() += ( n - 1 ) * ( size.height() + spacing );
if ( n > 0 )
{
h += maxSize.height();
if ( n > 1 )
h += ( n - 1 ) * ( bulletSize.height() + spacing );
} }
w = maxSize.width(); const auto hint = indicator->outerBoxSize( Q::Panel, size );
}
const auto hint = indicator->outerBoxSize( Q::Panel, QSizeF( w, h ) );
return hint.expandedTo( indicator->strutSizeHint( Q::Panel ) ); return hint.expandedTo( indicator->strutSizeHint( Q::Panel ) );
} }

View File

@ -8,8 +8,6 @@
#include "QskSkinlet.h" #include "QskSkinlet.h"
class QskPageIndicator;
class QSK_EXPORT QskPageIndicatorSkinlet : public QskSkinlet class QSK_EXPORT QskPageIndicatorSkinlet : public QskSkinlet
{ {
Q_GADGET Q_GADGET
@ -32,13 +30,20 @@ class QSK_EXPORT QskPageIndicatorSkinlet : public QskSkinlet
QSizeF sizeHint( const QskSkinnable*, QSizeF sizeHint( const QskSkinnable*,
Qt::SizeHint, const QSizeF& ) const override; Qt::SizeHint, const QSizeF& ) const override;
int sampleCount( const QskSkinnable*, QskAspect::Subcontrol ) const override;
QRectF sampleRect( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol, int index ) const override;
int sampleIndexAt( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol, const QPointF& ) const override;
protected: protected:
QSGNode* updateSubNode( const QskSkinnable*, QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override; quint8 nodeRole, QSGNode* ) const override;
private: QSGNode* updateSampleNode( const QskSkinnable*,
QSGNode* updateBulletsNode( const QskPageIndicator*, QSGNode* ) const; QskAspect::Subcontrol, int index, QSGNode* ) const override;
QRectF bulletRect( const QskPageIndicator*, const QRectF&, int index ) const;
}; };
#endif #endif