nodes/QskScaleRenderer.cpp

Functions

  Name
QSGNode * qskRemoveTraillingNodes(QSGNode * node, QSGNode * childNode)
void qskInsertRemoveChild(QSGNode * parentNode, QSGNode * oldNode, QSGNode * newNode, bool append)

Functions Documentation

function qskRemoveTraillingNodes

static QSGNode * qskRemoveTraillingNodes(
    QSGNode * node,
    QSGNode * childNode
)

function qskInsertRemoveChild

static inline void qskInsertRemoveChild(
    QSGNode * parentNode,
    QSGNode * oldNode,
    QSGNode * newNode,
    bool append
)

Source code

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

#include "QskScaleRenderer.h"
#include "QskSkinlet.h"
#include "QskSGNode.h"
#include "QskTickmarksNode.h"
#include "QskTextNode.h"
#include "QskGraphicNode.h"
#include "QskTextOptions.h"
#include "QskGraphic.h"
#include "QskControl.h"
#include "QskFunctions.h"

#include <qstring.h>
#include <qfontmetrics.h>

static QSGNode* qskRemoveTraillingNodes( QSGNode* node, QSGNode* childNode )
{
    QskSGNode::removeAllChildNodesFrom( node, childNode );
    return nullptr;
}

static inline void qskInsertRemoveChild( QSGNode* parentNode,
    QSGNode* oldNode, QSGNode* newNode, bool append )
{
    if ( newNode == oldNode )
        return;

    if ( oldNode )
    {
        parentNode->removeChildNode( oldNode );
        if ( oldNode->flags() & QSGNode::OwnedByParent )
            delete oldNode;
    }

    if ( newNode )
    {
        if ( append )
            parentNode->appendChildNode( newNode );
        else
            parentNode->prependChildNode( newNode );
    }
}

void QskScaleRenderer::setOrientation( Qt::Orientation orientation )
{
    m_orientation = orientation;
}

void QskScaleRenderer::setBoundaries( const QskIntervalF& boundaries )
{
    m_boundaries = boundaries;
}

void QskScaleRenderer::setTickmarks( const QskScaleTickmarks& tickmarks )
{
    m_tickmarks = tickmarks;
}

void QskScaleRenderer::setTickColor( const QColor& color )
{
    m_tickColor = color;
}

void QskScaleRenderer::setTickWidth( qreal width )
{
    m_tickWidth = width;
}

void QskScaleRenderer::setFont( const QFont& font )
{
    m_font = font;
}

void QskScaleRenderer::setTextColors( const QskTextColors& textColors )
{
    m_textColors = textColors;
}

void QskScaleRenderer::setColorFilter( const QskColorFilter& colorFilter )
{
    m_colorFilter = colorFilter;
}

QSGNode* QskScaleRenderer::updateScaleNode(
    const QskSkinnable* skinnable, const QRectF& tickmarksRect,
    const QRectF& labelsRect, QSGNode* node )
{
    enum Role
    {
        Ticks = 1,
        Labels = 2
    };

    if ( node == nullptr )
        node = new QSGNode();

    {
        QSGNode* oldNode = QskSGNode::findChildNode( node, Ticks );
        QSGNode* newNode = nullptr;

        if ( !tickmarksRect.isEmpty() )
        {
            newNode = updateTicksNode( skinnable, tickmarksRect, oldNode );
            if ( newNode )
                QskSGNode::setNodeRole( newNode, Ticks );
        }

        qskInsertRemoveChild( node, oldNode, newNode, false );
    }

    {
        QSGNode* oldNode = QskSGNode::findChildNode( node, Labels );
        QSGNode* newNode = nullptr;

        if ( !labelsRect.isEmpty() )
        {
            newNode = updateLabelsNode( skinnable, tickmarksRect, labelsRect, oldNode );
            if ( newNode )
                QskSGNode::setNodeRole( newNode, Labels );
        }

        qskInsertRemoveChild( node, oldNode, newNode, true );
    }

    return node;
}

QSGNode* QskScaleRenderer::updateTicksNode(
    const QskSkinnable*, const QRectF& rect, QSGNode* node ) const
{
    if ( rect.isEmpty() )
        return nullptr;

    auto ticksNode = static_cast< QskTickmarksNode* >( node );

    if( ticksNode == nullptr )
        ticksNode = new QskTickmarksNode;

    ticksNode->update( m_tickColor, rect, m_boundaries,
        m_tickmarks, m_tickWidth, m_orientation );

    return ticksNode;
}

QSGNode* QskScaleRenderer::updateLabelsNode(
    const QskSkinnable* skinnable, const QRectF& tickmarksRect,
    const QRectF& labelsRect, QSGNode* node ) const
{
    if ( labelsRect.isEmpty() || tickmarksRect.isEmpty() )
        return nullptr;

    const auto ticks = m_tickmarks.majorTicks();
    if ( ticks.isEmpty() )
        return nullptr;

    if( node == nullptr )
        node = new QSGNode;

    const QFontMetricsF fm( m_font );

    const qreal length = ( m_orientation == Qt::Horizontal )
        ? tickmarksRect.width() : tickmarksRect.height();
    const qreal ratio = length / m_boundaries.width();

    auto nextNode = node->firstChild();

    QRectF labelRect;

    for ( auto tick : ticks )
    {
        enum LabelNodeRole
        {
            TextNode = 1,
            GraphicNode = 2
        };

        const auto label = labelAt( tick );
        if ( label.isNull() )
            continue;

        const qreal tickPos = ratio * ( tick - m_boundaries.lowerBound() );

        if ( label.canConvert< QString >() )
        {
            auto text = label.toString();
            if ( text.isEmpty() )
                continue;

            QRectF r;
            Qt::Alignment alignment;

            if( m_orientation == Qt::Horizontal )
            {
                const auto w = qskHorizontalAdvance( fm, text );

                auto pos = tickmarksRect.x() + tickPos - 0.5 * w;
                pos = qBound( labelsRect.left(), pos, labelsRect.right() - w );

                r = QRectF( pos, labelsRect.y(), w, labelsRect.height() );

                alignment = Qt::AlignLeft;
            }
            else
            {
                const auto h = fm.height();

                auto pos = tickmarksRect.bottom() - ( tickPos + 0.5 * h );

                /*
                    when clipping the label we can expand the clip rectangle
                    by the ascent/descent margins, as nothing gets painted there
                    anyway.
                 */
                const qreal min = labelsRect.top() - ( h - fm.ascent() );
                const qreal max = labelsRect.bottom() + fm.descent();
                pos = qBound( min, pos, max );

                r = QRectF( labelsRect.x(), pos, labelsRect.width(), h );

                alignment = Qt::AlignRight;
            }

            if ( nextNode && QskSGNode::nodeRole( nextNode ) != TextNode )
            {
                nextNode = qskRemoveTraillingNodes( node, nextNode );
            }

            if ( !labelRect.isEmpty() && labelRect.intersects( r ) )
            {
                if ( tick != ticks.last() )
                {
                    text = QString();
                }
                else
                {
                    if ( auto obsoleteNode = nextNode
                        ? nextNode->previousSibling() : node->lastChild() )
                    {
                        node->removeChildNode( obsoleteNode );
                        if ( obsoleteNode->flags() & QSGNode::OwnedByParent )
                            delete obsoleteNode;
                    }

                    labelRect = r;
                }
            }
            else
            {
                labelRect = r;
            }

            if ( nextNode == nullptr )
            {
                nextNode = new QskTextNode;
                QskSGNode::setNodeRole( nextNode, TextNode );
                node->appendChildNode( nextNode );
            }

            auto textNode = static_cast< QskTextNode* >( nextNode );
            textNode->setTextData( skinnable->owningControl(), text, r, m_font,
                QskTextOptions(), m_textColors, alignment, Qsk::Normal );

            nextNode = nextNode->nextSibling();
        }
        else if ( label.canConvert< QskGraphic >() )
        {
            const auto graphic = label.value< QskGraphic >();
            if ( graphic.isNull() )
                continue;

            const auto h = fm.height();
            const auto w = graphic.widthForHeight( h );

            Qt::Alignment alignment;

            if( m_orientation == Qt::Horizontal )
            {
                auto pos = tickmarksRect.x() + tickPos - 0.5 * w;
                pos = qBound( labelsRect.left(), pos, labelsRect.right() - w );

                labelRect = QRectF( pos, labelsRect.y(), w, h );
                alignment = Qt::AlignHCenter | Qt::AlignBottom;
            }
            else
            {
                auto pos = tickmarksRect.bottom() - ( tickPos + 0.5 * h );
                pos = qBound( labelsRect.top(), pos, labelsRect.bottom() - h );

                labelRect = QRectF( labelsRect.right() - w, pos, w, h );
                alignment = Qt::AlignRight | Qt::AlignVCenter;
            }

            if ( nextNode && QskSGNode::nodeRole( nextNode ) != GraphicNode )
            {
                nextNode = qskRemoveTraillingNodes( node, nextNode );
            }

            if ( nextNode == nullptr )
            {
                nextNode = new QskGraphicNode;
                QskSGNode::setNodeRole( nextNode, GraphicNode );
                node->appendChildNode( nextNode );
            }

            auto graphicNode = static_cast< QskGraphicNode* >( nextNode );

            QskSkinlet::updateGraphicNode(
                skinnable->owningControl(), graphicNode,
                graphic, m_colorFilter, labelRect, alignment );

            nextNode = nextNode->nextSibling();
        }
    }

    qskRemoveTraillingNodes( node, nextNode );

    return node;
}

QVariant QskScaleRenderer::labelAt( qreal pos ) const
{
    return QString::number( pos, 'g' );
}

QSizeF QskScaleRenderer::boundingLabelSize() const
{
    const auto ticks = m_tickmarks.majorTicks();
    if ( ticks.isEmpty() )
        return QSizeF( 0.0, 0.0 );

    const QFontMetricsF fm( m_font );

    qreal maxWidth = 0.0;
    const qreal h = fm.height();

    for ( auto tick : ticks )
    {
        qreal w = 0.0;

        const auto label = labelAt( tick );
        if ( label.isNull() )
            continue;

        if ( label.canConvert< QString >() )
        {
            w = qskHorizontalAdvance( fm, label.toString() );
        }
        else if ( label.canConvert< QskGraphic >() )
        {
            const auto graphic = label.value< QskGraphic >();
            if ( !graphic.isNull() )
            {
                w = graphic.widthForHeight( h );
            }
        }

        maxWidth = qMax( w, maxWidth );
    }

    return QSizeF( maxWidth, h );
}

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