controls/QskPageIndicatorSkinlet.cpp

Source code

/******************************************************************************
 * QSkinny - Copyright (C) 2016 Uwe Rathmann
 * This file may be used under the terms of the QSkinny License, Version 1.0
 *****************************************************************************/

#include "QskPageIndicatorSkinlet.h"
#include "QskPageIndicator.h"

#include "QskBoxNode.h"
#include "QskSGNode.h"

QskPageIndicatorSkinlet::QskPageIndicatorSkinlet( QskSkin* skin )
    : QskSkinlet( skin )
{
    setNodeRoles( { PanelRole, BulletsRole } );
}

QskPageIndicatorSkinlet::~QskPageIndicatorSkinlet()
{
}

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 );
}

QSGNode* QskPageIndicatorSkinlet::updateSubNode(
    const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
    const auto indicator = static_cast< const QskPageIndicator* >( skinnable );

    switch ( nodeRole )
    {
        case PanelRole:
        {
            return updateBoxNode( indicator, node, QskPageIndicator::Panel );
        }

        case BulletsRole:
        {
            return updateBulletsNode( indicator, node );
        }
    }

    return Inherited::updateSubNode( skinnable, nodeRole, node );
}

QRectF QskPageIndicatorSkinlet::bulletRect(
    const QskPageIndicator* indicator, const QRectF& rect, int index ) 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 )
    {
        w = ( indicator->count() - 1 ) * ( wNormal + spacing ) + wHighlighted;
        h = rect.height();
    }
    else
    {
        w = rect.width();
        h = ( indicator->count() - 1 ) * ( hNormal + spacing ) + hHighlighted;
    }

    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;
}

QSGNode* QskPageIndicatorSkinlet::updateBulletsNode(
    const QskPageIndicator* indicator, QSGNode* node ) 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;

        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 );
    }

    // if count has decreased we need to remove superfluous nodes
    QskSGNode::removeAllChildNodesAfter( node, bulletNode );

    return node;
}

QSizeF QskPageIndicatorSkinlet::sizeHint( const QskSkinnable* skinnable,
    Qt::SizeHint which, const QSizeF& ) const
{
    using Q = QskPageIndicator;

    if ( which != Qt::PreferredSize )
        return QSizeF();

    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 );

    const int n = indicator->count();

    qreal w = 0;
    qreal h = 0;

    if ( indicator->orientation() == Qt::Horizontal )
    {
        if ( n > 0 )
        {
            w += maxSize.width();

            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();
    }

    const auto hint = indicator->outerBoxSize( Q::Panel, QSizeF( w, h ) );
    return hint.expandedTo( indicator->strutSizeHint( Q::Panel ) );
}

#include "moc_QskPageIndicatorSkinlet.cpp"

Updated on 28 July 2023 at 14:02:29 CEST