From 1feccd942a96410ec5bfe407271c7ed8b7368fb5 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 4 Jan 2022 13:58:34 +0100 Subject: [PATCH] QskPageIndicator improved --- examples/layouts/StackLayoutPage.cpp | 3 +- skins/material/QskMaterialSkin.cpp | 28 +-- skins/squiek/QskSquiekSkin.cpp | 25 +-- src/controls/QskPageIndicator.cpp | 45 +++- src/controls/QskPageIndicator.h | 13 +- src/controls/QskPageIndicatorSkinlet.cpp | 255 +++++++++-------------- src/controls/QskPageIndicatorSkinlet.h | 15 +- 7 files changed, 181 insertions(+), 203 deletions(-) diff --git a/examples/layouts/StackLayoutPage.cpp b/examples/layouts/StackLayoutPage.cpp index 0b091848..5d31eecf 100644 --- a/examples/layouts/StackLayoutPage.cpp +++ b/examples/layouts/StackLayoutPage.cpp @@ -184,13 +184,12 @@ StackLayoutPage::StackLayoutPage( QQuickItem* parent ) auto progressBar = new ProgressBar(); progressBar->setMargins( QMarginsF( 0, 0, 10, 0 ) ); - addItem( buttonBox ); addItem( pageIndicator ); addItem( box ); addItem( progressBar, Qt::AlignRight ); - connect( box, &QskStackBox::currentIndexChanged, + connect( box, &QskStackBox::transientIndexChanged, pageIndicator, &QskPageIndicator::setCurrentIndex ); connect( box, &StackBox::transitionStarted, diff --git a/skins/material/QskMaterialSkin.cpp b/skins/material/QskMaterialSkin.cpp index 42b72104..c5948549 100644 --- a/skins/material/QskMaterialSkin.cpp +++ b/skins/material/QskMaterialSkin.cpp @@ -281,27 +281,19 @@ void Editor::setupPageIndicator() { using Q = QskPageIndicator; - for ( auto subControl : { Q::Bullet, Q::Highlighted } ) - { - const auto extent = qskDpiScaled( 10 ); - setStrutSize( subControl, extent, extent ); + const auto extent = qskDpiScaled( 9 ); + setStrutSize( Q::Bullet, extent, extent ); - // circles, without border - setBoxShape( subControl, 100, Qt::RelativeSize ); - setBoxBorderMetrics( subControl, 0 ); + // circles, without border + setBoxShape( Q::Bullet, 100, Qt::RelativeSize ); + setBoxBorderMetrics( Q::Bullet, 0 ); - const QColor color = ( subControl == Q::Bullet ) - ? m_pal.lighter150 : m_pal.accentColor; + setGradient( Q::Bullet, m_pal.lighter150 ); + setGradient( Q::Bullet | Q::Selected, m_pal.accentColor ); - setGradient( subControl, color ); - setBoxBorderColors( subControl, color ); - } - - // no visible background panel - setBoxBorderMetrics( Q::Panel, 0 ); - setGradient( Q::Panel, QskGradient() ); - - setSpacing( Q::Panel, 3 ); + setSpacing( Q::Panel, 5 ); + setPadding( Q::Panel, 0 ); + setGradient( Q::Panel, QskGradient() ); // invisible } void Editor::setupPushButton() diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index 779d97cf..64e59018 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -441,27 +441,24 @@ void Editor::setupSeparator() void Editor::setupPageIndicator() { - using A = QskAspect; using Q = QskPageIndicator; - for ( auto subControl : { Q::Bullet, Q::Highlighted } ) - { - const auto extent = qskDpiScaled( 12 ); - setStrutSize( subControl, extent, extent ); + const auto extent = qskDpiScaled( 8 ); + setStrutSize( Q::Bullet, extent, extent ); - setBoxBorderMetrics( subControl, 50, Qt::RelativeSize ); - setBoxShape( subControl, 100, Qt::RelativeSize ); - } + // circles, without border + setBoxShape( Q::Bullet, 100, Qt::RelativeSize ); + setBoxBorderMetrics( Q::Bullet, 0 ); setGradient( Q::Bullet, m_pal.darker150 ); - setGradient( Q::Highlighted, m_pal.lighter150 ); + setMargin( Q::Bullet, qskDpiScaled( 1 ) ); - // no visible background panel - setBoxBorderMetrics( Q::Panel, 0 ); - setBoxShape( Q::Panel, 2 ); - setGradient( Q::Panel, QskGradient() ); + setGradient( Q::Bullet | Q::Selected, m_pal.lighter150 ); + setMargin( Q::Bullet | Q::Selected, 0 ); - setMetric( Q::Panel | A::Spacing, 3 ); + setSpacing( Q::Panel, 3 ); + setPadding( Q::Panel, 0 ); + setGradient( Q::Panel, QskGradient() ); // invisible } void Editor::setupPushButton() diff --git a/src/controls/QskPageIndicator.cpp b/src/controls/QskPageIndicator.cpp index afd0d1b8..1b4961ea 100644 --- a/src/controls/QskPageIndicator.cpp +++ b/src/controls/QskPageIndicator.cpp @@ -8,20 +8,24 @@ QSK_SUBCONTROL( QskPageIndicator, Panel ) QSK_SUBCONTROL( QskPageIndicator, Bullet ) -QSK_SUBCONTROL( QskPageIndicator, Highlighted ) + +QSK_SYSTEM_STATE( QskPageIndicator, Selected, QskAspect::FirstSystemState << 1 ) class QskPageIndicator::PrivateData { public: PrivateData( int count ) : count( count ) - , currentIndex( -1 ) + , interactive( false ) , orientation( Qt::Horizontal ) { } + qreal currentIndex = -1; + int count; - qreal currentIndex; + + bool interactive : 1; 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 ) { 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" diff --git a/src/controls/QskPageIndicator.h b/src/controls/QskPageIndicator.h index f9c691ca..b46f6063 100644 --- a/src/controls/QskPageIndicator.h +++ b/src/controls/QskPageIndicator.h @@ -18,13 +18,17 @@ class QSK_EXPORT QskPageIndicator : public QskControl Q_PROPERTY( qreal currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged FINAL ) + Q_PROPERTY( bool interactive READ isInteractive + WRITE setInteractive NOTIFY interactiveChanged FINAL ) + Q_PROPERTY( Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL ) using Inherited = QskControl; public: - QSK_SUBCONTROLS( Panel, Bullet, Highlighted ) + QSK_SUBCONTROLS( Panel, Bullet ) + QSK_STATES( Selected ) QskPageIndicator( QQuickItem* parent = nullptr ); QskPageIndicator( int count, QQuickItem* parent = nullptr ); @@ -32,16 +36,21 @@ class QSK_EXPORT QskPageIndicator : public QskControl ~QskPageIndicator() override; int count() const; - qreal currentIndex() const; Qt::Orientation orientation() const; void setOrientation( Qt::Orientation ); + bool isInteractive() const; + void setInteractive( bool ); + + qreal valueRatioAt( int index ) const; + Q_SIGNALS: void countChanged( int ); void currentIndexChanged( qreal ); void orientationChanged( Qt::Orientation ); + void interactiveChanged( bool ); public Q_SLOTS: void setCount( int count ); diff --git a/src/controls/QskPageIndicatorSkinlet.cpp b/src/controls/QskPageIndicatorSkinlet.cpp index 4ffc5d0c..58eded2e 100644 --- a/src/controls/QskPageIndicatorSkinlet.cpp +++ b/src/controls/QskPageIndicatorSkinlet.cpp @@ -8,6 +8,50 @@ #include "QskBoxNode.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 ) : QskSkinlet( skin ) @@ -23,9 +67,7 @@ QRectF QskPageIndicatorSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { if ( subControl == QskPageIndicator::Panel ) - { return contentsRect; - } return Inherited::subControlRect( skinnable, contentsRect, subControl ); } @@ -33,157 +75,75 @@ QRectF QskPageIndicatorSkinlet::subControlRect( const QskSkinnable* skinnable, QSGNode* QskPageIndicatorSkinlet::updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const { - const auto indicator = static_cast< const QskPageIndicator* >( skinnable ); + using Q = QskPageIndicator; switch ( nodeRole ) { case PanelRole: - { - return updateBoxNode( indicator, node, QskPageIndicator::Panel ); - } + return updateBoxNode( skinnable, node, Q::Panel ); case BulletsRole: - { - return updateBulletsNode( indicator, node ); - } + return updateSeriesNode( skinnable, Q::Bullet, node ); } return Inherited::updateSubNode( skinnable, nodeRole, node ); } -QRectF QskPageIndicatorSkinlet::bulletRect( - const QskPageIndicator* indicator, const QRectF& rect, int index ) const +int QskPageIndicatorSkinlet::sampleCount( + const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const { using Q = QskPageIndicator; - const auto szNormal = indicator->strutSizeHint( 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 ) + if ( subControl == Q::Bullet ) { - w = ( indicator->count() - 1 ) * ( wNormal + spacing ) + wHighlighted; - h = rect.height(); - } - else - { - w = rect.width(); - h = ( indicator->count() - 1 ) * ( hNormal + spacing ) + hHighlighted; + const auto indicator = static_cast< const QskPageIndicator* >( skinnable ); + return indicator->count(); } - QRectF r( 0, 0, w, h ); - 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; + return Inherited::sampleCount( skinnable, subControl ); } -QSGNode* QskPageIndicatorSkinlet::updateBulletsNode( - const QskPageIndicator* indicator, QSGNode* node ) const +QRectF QskPageIndicatorSkinlet::sampleRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const { - if ( indicator->count() == 0 ) - return nullptr; + using Q = QskPageIndicator; - 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++ ) + if ( subControl == Q::Bullet ) { - using Q = QskPageIndicator; + const auto indicator = static_cast< const QskPageIndicator* >( skinnable ); - if ( i > 0 ) - bulletNode = bulletNode->nextSibling(); - - if ( bulletNode == nullptr ) - bulletNode = new QskBoxNode(); - - updateBoxNode( indicator, bulletNode, bulletRect( indicator, rect, i ), - ( i == currentBullet ) ? Q::Highlighted : Q::Bullet ); - - if ( bulletNode->parent() != node ) - node->appendChildNode( bulletNode ); + const auto rect = indicator->subControlContentsRect( Q::Panel ); + return qskBulletRect( indicator, rect, index ); } - // if count has decreased we need to remove superfluous nodes - QskSGNode::removeAllChildNodesAfter( node, bulletNode ); + return Inherited::sampleRect( skinnable, contentsRect, subControl, index ); +} - 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, @@ -196,44 +156,21 @@ QSizeF QskPageIndicatorSkinlet::sizeHint( const QskSkinnable* skinnable, const auto indicator = static_cast< const QskPageIndicator* >( skinnable ); - const auto bulletSize = indicator->strutSizeHint( Q::Bullet ); - - const auto maxSize = bulletSize.expandedTo( - indicator->strutSizeHint( Q::Highlighted ) ); - - const qreal spacing = indicator->spacingHint( Q::Panel ); - + QSizeF size( 0.0, 0.0 ); const int n = indicator->count(); - qreal w = 0; - qreal h = 0; - - if ( indicator->orientation() == Qt::Horizontal ) + if ( n > 0 ) { - if ( n > 0 ) - { - w += maxSize.width(); + size = indicator->strutSizeHint( Q::Bullet ); + const qreal spacing = indicator->spacingHint( Q::Panel ); - if ( n > 1 ) - w += ( n - 1 ) * ( bulletSize.width() + spacing ); - } - - h = maxSize.height(); - } - else - { - if ( n > 0 ) - { - h += maxSize.height(); - - if ( n > 1 ) - h += ( n - 1 ) * ( bulletSize.height() + spacing ); - } - - w = maxSize.width(); + if ( indicator->orientation() == Qt::Horizontal ) + size.rwidth() += ( n - 1 ) * ( size.width() + spacing ); + else + size.rheight() += ( n - 1 ) * ( size.height() + spacing ); } - const auto hint = indicator->outerBoxSize( Q::Panel, QSizeF( w, h ) ); + const auto hint = indicator->outerBoxSize( Q::Panel, size ); return hint.expandedTo( indicator->strutSizeHint( Q::Panel ) ); } diff --git a/src/controls/QskPageIndicatorSkinlet.h b/src/controls/QskPageIndicatorSkinlet.h index 0685a309..9cdd91ae 100644 --- a/src/controls/QskPageIndicatorSkinlet.h +++ b/src/controls/QskPageIndicatorSkinlet.h @@ -8,8 +8,6 @@ #include "QskSkinlet.h" -class QskPageIndicator; - class QSK_EXPORT QskPageIndicatorSkinlet : public QskSkinlet { Q_GADGET @@ -32,13 +30,20 @@ class QSK_EXPORT QskPageIndicatorSkinlet : public QskSkinlet QSizeF sizeHint( const QskSkinnable*, 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: QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; - private: - QSGNode* updateBulletsNode( const QskPageIndicator*, QSGNode* ) const; - QRectF bulletRect( const QskPageIndicator*, const QRectF&, int index ) const; + QSGNode* updateSampleNode( const QskSkinnable*, + QskAspect::Subcontrol, int index, QSGNode* ) const override; }; #endif