From 7abd90b2dd8383e74967952bb224e32c98102c3a Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sun, 4 Dec 2022 19:54:05 +0100 Subject: [PATCH] Finally all types of gradients can be set to boxes. For radial/conic and tilted linear gradients shape/border metrics will be translated into a QPainterPath to be fed into the triangulation. However the borders remain being done by QskBoxRenderer. --- src/nodes/QskBoxFillNode.cpp | 183 +++++++++++++++++++++++++++++++++ src/nodes/QskBoxFillNode.h | 35 +++++++ src/nodes/QskShadedBoxNode.cpp | 132 +++++++++++++++++++++--- src/nodes/QskShadedBoxNode.h | 13 +-- src/src.pro | 2 + 5 files changed, 342 insertions(+), 23 deletions(-) create mode 100644 src/nodes/QskBoxFillNode.cpp create mode 100644 src/nodes/QskBoxFillNode.h diff --git a/src/nodes/QskBoxFillNode.cpp b/src/nodes/QskBoxFillNode.cpp new file mode 100644 index 00000000..058388aa --- /dev/null +++ b/src/nodes/QskBoxFillNode.cpp @@ -0,0 +1,183 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskBoxFillNode.h" +#include "QskGradientMaterial.h" +#include "QskGradient.h" +#include "QskGradientDirection.h" +#include "QskBoxShapeMetrics.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxRenderer.h" + +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +#include +QSK_QT_PRIVATE_END + +static inline QskHashValue qskMetricsHash( + const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& borderMetrics ) +{ + QskHashValue hash = 13000; + + hash = shape.hash( hash ); + return borderMetrics.hash( hash ); +} + +static inline QskGradient qskEffectiveGradient( const QskGradient& gradient ) +{ + if ( gradient.type() == QskGradient::Stops || gradient.isMonochrome() ) + { + // the shader for linear gradients is the fastest + QskGradient g; + g.setLinearDirection( Qt::Vertical ); + g.setStops( gradient.stops() ); + + return g; + } + + return gradient; +} + +static void qskUpdateGeometry( const QPainterPath& path, QSGGeometry& geometry ) +{ + const auto ts = qTriangulate( path, QTransform(), 1, false ); + + /* + As we have to iterate over the vertex buffer to copy qreal to float + anyway we reorder and drop the index buffer. + + QTriangleSet: + + vertices: (x[i[n]], y[i[n]]), (x[j[n]], y[j[n]]), (x[k[n]], y[k[n]]), n = 0, 1, ... + QVector vertices; // [x[0], y[0], x[1], y[1], x[2], ...] + QVector indices; // [i[0], j[0], k[0], i[1], j[1], k[1], i[2], ...] + */ + const auto points = ts.vertices.constData(); + const auto indices = reinterpret_cast< const quint16* >( ts.indices.data() ); + + geometry.allocate( ts.indices.size() ); + + auto vertexData = geometry.vertexDataAsPoint2D(); + for ( int i = 0; i < ts.indices.size(); i++ ) + { + const int j = 2 * indices[i]; + vertexData[i].set( points[j], points[j + 1] ); + } +} + +static inline void qskResetGeometry( QskBoxFillNode* node ) +{ + auto g = node->geometry(); + if ( g->vertexCount() > 0 ) + { + g->allocate( 0 ); + node->markDirty( QSGNode::DirtyGeometry ); + } +} + +class QskBoxFillNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskBoxFillNodePrivate() + : geometry( QSGGeometry::defaultAttributes_Point2D(), 0 ) + { + geometry.setDrawingMode( QSGGeometry::DrawTriangles ); + } + + QskHashValue metricsHash = 0; + QskHashValue gradientHash = 0; + QRectF rect; + + QSGGeometry geometry; + int gradientType = -1; +}; + +QskBoxFillNode::QskBoxFillNode() + : QSGGeometryNode( *new QskBoxFillNodePrivate ) +{ + Q_D( QskBoxFillNode ); + + setGeometry( &d->geometry ); + setMaterial( new QSGFlatColorMaterial() ); + setFlag( QSGNode::OwnsMaterial, true ); +} + +void QskBoxFillNode::updateNode( + const QRectF& rect, const QskBoxShapeMetrics& shapeMetrics, + const QskBoxBorderMetrics& borderMetrics, const QskGradient& gradient ) +{ + Q_D( QskBoxFillNode ); + + const auto effectiveGradient = qskEffectiveGradient( gradient ); + + const auto metricsHash = qskMetricsHash( shapeMetrics, borderMetrics ); + const auto gradientHash = effectiveGradient.hash( 17321 ); + + const bool dirtyGeometry = ( metricsHash != d->metricsHash ) || ( rect == d->rect ); + const bool dirtyMaterial = gradientHash != d->gradientHash; + + if ( !( dirtyGeometry || dirtyMaterial ) ) + return; + + d->metricsHash = metricsHash; + d->gradientHash = gradientHash; + d->rect = rect; + + if ( rect.isEmpty() || !effectiveGradient.isVisible() ) + { + qskResetGeometry( this ); + return; + } + + if ( dirtyGeometry ) + { + const auto path = QskBoxRenderer().fillPath( + rect, shapeMetrics, borderMetrics ); + + if ( path.isEmpty() ) + { + qskResetGeometry( this ); + return; + } + + qskUpdateGeometry( path, d->geometry ); + + markDirty( QSGNode::DirtyGeometry ); + } + + if ( dirtyMaterial ) + { + if ( effectiveGradient.isMonochrome() ) + { + if ( material() == nullptr || d->gradientType >= 0 ) + { + setMaterial( new QSGFlatColorMaterial() ); + d->gradientType = -1; + } + + auto colorMaterial = static_cast< QSGFlatColorMaterial* >( material() ); + colorMaterial->setColor( effectiveGradient.startColor().toRgb() ); + } + else + { + const auto gradientType = effectiveGradient.type(); + + if ( ( material() == nullptr ) || ( gradientType != d->gradientType ) ) + { + setMaterial( QskGradientMaterial::createMaterial( gradientType ) ); + d->gradientType = gradientType; + } + + auto gradientMaterial = static_cast< QskGradientMaterial* >( material() ); + gradientMaterial->updateGradient( rect, effectiveGradient ); + } + + markDirty( QSGNode::DirtyMaterial ); + } +} diff --git a/src/nodes/QskBoxFillNode.h b/src/nodes/QskBoxFillNode.h new file mode 100644 index 00000000..5c069f9c --- /dev/null +++ b/src/nodes/QskBoxFillNode.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_BOX_FILL_NODE_H +#define QSK_BOX_FILL_NODE_H + +#include "QskGlobal.h" +#include + +class QskGradient; +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; + +class QskBoxFillNodePrivate; + +class QSK_EXPORT QskBoxFillNode : public QSGGeometryNode +{ + public: + QskBoxFillNode(); + + void updateNode( const QRectF&, + const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, + const QskGradient& ); + + void updateNode( const QRectF&, + const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, + const QColor& ); + + private: + Q_DECLARE_PRIVATE( QskBoxFillNode ) +}; + +#endif diff --git a/src/nodes/QskShadedBoxNode.cpp b/src/nodes/QskShadedBoxNode.cpp index 1361cbfb..d7cf5cea 100644 --- a/src/nodes/QskShadedBoxNode.cpp +++ b/src/nodes/QskShadedBoxNode.cpp @@ -4,14 +4,92 @@ *****************************************************************************/ #include "QskShadedBoxNode.h" +#include "QskBoxFillNode.h" #include "QskBoxShadowNode.h" +#include "QskBoxNode.h" +#include "QskSGNode.h" + +#include "QskGradient.h" +#include "QskGradientDirection.h" #include "QskShadowMetrics.h" -#include +#include "QskBoxBorderMetrics.h" +#include "QskBoxBorderColors.h" + +namespace +{ + enum Role + { + ShadowRole, + BoxRole, + FillRole + }; +} + +void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node ) +{ + static const QVector< quint8 > roles = { ShadowRole, BoxRole, FillRole }; + + auto oldNode = QskSGNode::findChildNode( parentNode, role ); + QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node ); +} + +template< typename Node > +inline Node* qskNode( QSGNode* parentNode, quint8 role ) +{ + using namespace QskSGNode; + + auto node = static_cast< Node* > ( findChildNode( parentNode, ShadowRole ) ); + + if ( node == nullptr ) + { + node = new Node(); + setNodeRole( node, role ); + } + + return node; +} + +static inline void qskInsertNode( QSGNode* parentNode, QSGNode* node ) +{ + static const QVector< quint8 > roles = { ShadowRole, BoxRole, FillRole }; + + QskSGNode::replaceChildNode( roles, QskSGNode::nodeRole( node ), + parentNode, nullptr, node ); +} + +static inline bool qskIsBoxGradient( const QskGradient& gradient ) +{ + if ( !gradient.isVisible() || gradient.isMonochrome() ) + return true; + + switch( gradient.type() ) + { + case QskGradient::Linear: + { + auto dir = gradient.linearDirection(); + + if ( dir.isTilted() ) + { + // only diagonal from topLeft to bottomRight + return ( dir.x1() == dir.x2() ) && ( dir.y1() == dir.y2() ); + } + + return true; + } + case QskGradient::Radial: + case QskGradient::Conic: + { + return false; + } + default: + { + return true; + } + } +} QskShadedBoxNode::QskShadedBoxNode() { - m_boxNode.setFlag( QSGNode::OwnedByParent, false ); - appendChildNode( &m_boxNode ); } QskShadedBoxNode::~QskShadedBoxNode() @@ -23,27 +101,47 @@ void QskShadedBoxNode::setBoxData( const QRectF& rect, const QskBoxBorderColors& borderColors, const QskGradient& gradient, const QskShadowMetrics& shadowMetrics, const QColor& shadowColor ) { - m_boxNode.setBoxData( rect, shape, borderMetrics, borderColors, gradient ); + using namespace QskSGNode; - if ( shadowMetrics.isNull() - || !shadowColor.isValid() || shadowColor.alpha() == 0 ) + QskBoxShadowNode* shadowNode = nullptr; + QskBoxNode* boxNode = nullptr; + QskBoxFillNode* fillNode = nullptr; + + if ( !shadowMetrics.isNull() + && shadowColor.isValid() && shadowColor.alpha() != 0 ) { - if ( m_shadowNode ) - { - removeChildNode( m_shadowNode ); - delete m_shadowNode; - m_shadowNode = nullptr; - } + shadowNode = qskNode< QskBoxShadowNode >( this, ShadowRole ); + shadowNode->setShadowData( shadowMetrics.shadowRect( rect ), + shape, shadowMetrics.blurRadius(), shadowColor ); + } + + /* + QskBoxNode supports vertical/horizontal/diagonal gradients only. + If our gradient doesn't fall into this category we use a QskBoxFillNode. + However the border is always done with a QskBoxNode + */ + + if ( qskIsBoxGradient( gradient ) ) + { + boxNode = qskNode< QskBoxNode >( this, BoxRole ); + boxNode->setBoxData( rect, shape, borderMetrics, borderColors, gradient ); } else { - if ( m_shadowNode == nullptr ) + if ( !borderMetrics.isNull() && borderColors.isVisible() ) { - m_shadowNode = new QskBoxShadowNode(); - insertChildNodeBefore( m_shadowNode, &m_boxNode ); + boxNode = qskNode< QskBoxNode >( this, BoxRole ); + boxNode->setBoxData( rect, shape, borderMetrics, borderColors, QskGradient() ); } - m_shadowNode->setShadowData( shadowMetrics.shadowRect( rect ), - shape, shadowMetrics.blurRadius(), shadowColor ); + if ( gradient.isVisible() ) + { + fillNode = qskNode< QskBoxFillNode >( this, FillRole ); + fillNode->updateNode( rect, shape, borderMetrics, gradient ); + } } + + qskUpdateChildren( this, ShadowRole, shadowNode ); + qskUpdateChildren( this, BoxRole, boxNode ); + qskUpdateChildren( this, FillRole, fillNode ); } diff --git a/src/nodes/QskShadedBoxNode.h b/src/nodes/QskShadedBoxNode.h index 3092b112..c4a67ee0 100644 --- a/src/nodes/QskShadedBoxNode.h +++ b/src/nodes/QskShadedBoxNode.h @@ -7,10 +7,15 @@ #define QSK_SHADED_BOX_NODE_H #include "QskGlobal.h" -#include "QskBoxNode.h" +#include -class QskBoxShadowNode; class QskShadowMetrics; +class QskBoxShapeMetrics; +class QskBoxBorderMetrics; +class QskBoxBorderColors; +class QskGradient; +class QskShadowMetrics; +class QColor; class QSK_EXPORT QskShadedBoxNode : public QSGNode { @@ -22,10 +27,6 @@ class QSK_EXPORT QskShadedBoxNode : public QSGNode const QskBoxShapeMetrics&, const QskBoxBorderMetrics&, const QskBoxBorderColors&, const QskGradient&, const QskShadowMetrics&, const QColor& shadowColor ); - - private: - QskBoxNode m_boxNode; - QskBoxShadowNode* m_shadowNode = nullptr; }; #endif diff --git a/src/src.pro b/src/src.pro index 02a76ae2..04c48aa8 100644 --- a/src/src.pro +++ b/src/src.pro @@ -103,6 +103,7 @@ HEADERS += \ nodes/QskArcRenderer.h \ nodes/QskBoxNode.h \ nodes/QskBoxClipNode.h \ + nodes/QskBoxFillNode.h \ nodes/QskBoxRenderer.h \ nodes/QskBoxRendererColorMap.h \ nodes/QskBoxShadowNode.h \ @@ -128,6 +129,7 @@ SOURCES += \ nodes/QskArcRenderer.cpp \ nodes/QskBoxNode.cpp \ nodes/QskBoxClipNode.cpp \ + nodes/QskBoxFillNode.cpp \ nodes/QskBoxRendererRect.cpp \ nodes/QskBoxRendererEllipse.cpp \ nodes/QskBoxRendererDEllipse.cpp \