From b69f84e865a85c3955a028e45da6b5f3ba770e3f Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 23 Sep 2024 16:04:09 +0200 Subject: [PATCH] handling of arcs improved --- playground/charts/CircularChartSkinlet.cpp | 2 +- src/nodes/QskArcNode.cpp | 149 ++++++------ src/nodes/QskArcRenderNode.cpp | 253 +++++++++++++++------ src/nodes/QskArcRenderNode.h | 8 +- src/nodes/QskArcRenderer.cpp | 75 ++++-- src/nodes/QskArcRenderer.h | 18 +- 6 files changed, 344 insertions(+), 161 deletions(-) diff --git a/playground/charts/CircularChartSkinlet.cpp b/playground/charts/CircularChartSkinlet.cpp index efdabda4..f78e228a 100644 --- a/playground/charts/CircularChartSkinlet.cpp +++ b/playground/charts/CircularChartSkinlet.cpp @@ -199,7 +199,7 @@ QSGNode* CircularChartSkinlet::updateArcSegmentNode( const QskSkinnable*, if ( arcNode == nullptr ) arcNode = new QskArcRenderNode(); - arcNode->updateNode( m_data->closedArcRect, metrics, true, + arcNode->updateArc( m_data->closedArcRect, metrics, true, borderWidth, borderColor, gradient ); return arcNode; diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index c2dc3f7a..d8328bd7 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -10,7 +10,6 @@ #include "QskArcRenderer.h" #include "QskMargins.h" #include "QskGradient.h" -#include "QskShapeNode.h" #include "QskSGNode.h" #include "QskShadowMetrics.h" @@ -22,40 +21,38 @@ namespace { ShadowRole, - PathRole, - ArcRole + /* + If possible border + filling will be displayed by ArcRole + Otherwise ArcRole displays the border and FillRole the filling + */ + + ArcRole, + FillRole }; } -static inline QskGradient qskEffectiveGradient( - const QskGradient& gradient, const QskArcMetrics& metrics ) +static void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node ) { - if ( !gradient.isMonochrome() ) - { - if ( gradient.type() == QskGradient::Stops ) - { - QskGradient g( gradient.stops() ); - g.setConicDirection( 0.5, 0.5, metrics.startAngle(), 360.0 ); + static const QVector< quint8 > roles = { ShadowRole, ArcRole, FillRole }; - return g; - } - } - - return gradient; + auto oldNode = QskSGNode::findChildNode( parentNode, role ); + QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node ); } template< typename Node > -inline Node* qskInsertOrRemoveNode( QSGNode* parentNode, quint8 role, bool isValid ) +inline Node* qskNode( QSGNode* parentNode, quint8 role ) { using namespace QskSGNode; - Node* oldNode = static_cast< Node* >( findChildNode( parentNode, role ) ); - Node* newNode = isValid ? ensureNode< Node >( oldNode ) : nullptr; + auto node = static_cast< Node* > ( findChildNode( parentNode, role ) ); - static const QVector< quint8 > roles = { ShadowRole, PathRole, ArcRole }; - replaceChildNode( roles, role, parentNode, oldNode, newNode ); + if ( node == nullptr ) + { + node = new Node(); + setNodeRole( node, role ); + } - return newNode; + return node; } QskArcNode::QskArcNode() @@ -82,60 +79,68 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient, const QColor& shadowColor, const QskShadowMetrics& shadowMetrics ) { - const bool radial = false; + using namespace QskSGNode; - const auto metricsArc = arcMetrics.toAbsolute( rect.size() ); + QskArcShadowNode* shadowNode = nullptr; + QskArcRenderNode* arcNode = nullptr; + QskArcRenderNode* fillNode = nullptr; - if ( metricsArc.isNull() || rect.isEmpty() ) + if ( !( rect.isEmpty() || arcMetrics.isNull() ) ) { - delete QskSGNode::findChildNode( this, ShadowRole ); - delete QskSGNode::findChildNode( this, PathRole ); - delete QskSGNode::findChildNode( this, ArcRole ); + const bool radial = false; + const auto metricsArc = arcMetrics.toAbsolute( rect.size() ); - return; + const auto hasFilling = gradient.isVisible(); + const auto hasBorder = ( borderWidth > 0.0 ) + && borderColor.isValid() && ( borderColor.alpha() > 0 ); + + const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 ); + + if ( hasShadow && hasFilling ) + { + /* + The shader of the shadow node is for circular arcs and we have some + unwanted scaling issues for the spread/blur values when having ellipsoid + arcs. We might also want to add the spread value to the ends of the arc + and not only to its radius. TODO ... + */ + + shadowNode = qskNode< QskArcShadowNode >( this, ShadowRole ); + + const auto sm = shadowMetrics.toAbsolute( rect.size() ); + const auto shadowRect = sm.shadowRect( rect ); + const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness(); + + shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(), + metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); + } + + if ( hasBorder || hasFilling ) + { + arcNode = qskNode< QskArcRenderNode >( this, ArcRole ); + + if ( hasBorder && hasFilling ) + { + if ( !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) ) + fillNode = qskNode< QskArcRenderNode >( this, FillRole ); + } + + if ( fillNode ) + { + arcNode->updateBorder( rect, metricsArc, + radial, borderWidth, borderColor ); + + fillNode->updateFilling( rect, metricsArc, radial, borderWidth, gradient ); + } + else + { + arcNode->updateArc( rect, metricsArc, + radial, borderWidth, borderColor, gradient ); + } + } } - const auto hasFilling = gradient.isVisible(); - const auto hasBorder = ( borderWidth > 0.0 ) - && borderColor.isValid() && ( borderColor.alpha() > 0 ); - const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 ); - - auto shadowNode = qskInsertOrRemoveNode< QskArcShadowNode >( - this, ShadowRole, hasFilling && hasShadow ); - - if ( shadowNode ) - { - /* - The shader of the shadow node is for circular arcs and we have some - unwanted scaling issues for the spread/blur values when having ellipsoid - arcs. We might also want to add the spread value to the ends of the arc - and not only to its radius. TODO ... - */ - - const auto sm = shadowMetrics.toAbsolute( rect.size() ); - const auto shadowRect = sm.shadowRect( rect ); - const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness(); - - shadowNode->setShadowData( shadowRect, spreadRadius, sm.blurRadius(), - metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); - } - - auto pathNode = qskInsertOrRemoveNode< QskShapeNode >( this, PathRole, - hasFilling && !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) ); - - if ( pathNode ) - { - const auto path = metricsArc.painterPath( rect, radial ); - pathNode->updateNode( path, QTransform(), rect, - qskEffectiveGradient( gradient, metricsArc ) ); - } - - auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >( - this, ArcRole, hasBorder || ( hasFilling && !pathNode ) ); - - if ( arcNode ) - { - arcNode->updateNode( rect, metricsArc, radial, - borderWidth, borderColor, pathNode ? QskGradient() : gradient ); - } + qskUpdateChildren( this, ShadowRole, shadowNode ); + qskUpdateChildren( this, ArcRole, arcNode ); + qskUpdateChildren( this, FillRole, fillNode ); } diff --git a/src/nodes/QskArcRenderNode.cpp b/src/nodes/QskArcRenderNode.cpp index 0d7ffddd..4da98336 100644 --- a/src/nodes/QskArcRenderNode.cpp +++ b/src/nodes/QskArcRenderNode.cpp @@ -11,12 +11,61 @@ #include "QskSGNode.h" #include "QskFillNodePrivate.h" +static inline bool qskHasBorder( qreal width, const QColor& color ) +{ + return ( width > 0.0 ) && color.isValid() && ( color.alpha() > 0 ); +} + class QskArcRenderNodePrivate final : public QskFillNodePrivate { public: - inline void resetValues() { hash = 0; } + inline void resetNode( QskArcRenderNode* node ) + { + m_metricsHash = m_colorsHash = 0; + node->resetGeometry(); + } - QskHashValue hash = 0; + inline bool updateMetrics( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth ) + { + QskHashValue hash = 13000; + + hash = qHashBits( &rect, sizeof( rect ), hash ); + hash = metrics.hash( hash ); + hash = qHash( radial, hash ); + hash = qHash( borderWidth, hash ); + + return updateHash( m_metricsHash, hash ); + } + + inline bool updateColors( const QColor& borderColor, const QskGradient& gradient ) + { + QskHashValue hash = 13000; + + if ( borderColor.isValid() && ( borderColor.alpha() > 0 ) ) + hash = qHashBits( &borderColor, sizeof( borderColor ), hash ); + + if ( gradient.isVisible() ) + hash = gradient.hash( hash ); + + return updateHash( m_colorsHash, hash ); + } + + private: + inline bool updateHash( QskHashValue& value, const QskHashValue newValue ) const + { + if ( newValue != value ) + { + value = newValue; + return true; + } + + return false; + } + + public: + QskHashValue m_metricsHash = 0; + QskHashValue m_colorsHash = 0; }; QskArcRenderNode::QskArcRenderNode() @@ -28,94 +77,168 @@ QskArcRenderNode::~QskArcRenderNode() { } -void QskArcRenderNode::updateNode( const QRectF& rect, +void QskArcRenderNode::updateFilling( const QRectF& rect, const QskArcMetrics& metrics, const QskGradient& gradient ) { - updateNode( rect, metrics, false, 0.0, QColor(), gradient ); + updateFilling( rect, metrics, false, 0.0, gradient ); } -void QskArcRenderNode::updateNode( const QRectF& rect, - const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor ) +void QskArcRenderNode::updateFilling( const QRectF& rect, + const QskArcMetrics& arcMetrics, bool radial, + qreal borderWidth, const QskGradient& gradient ) { - updateNode( rect, metrics, false, borderWidth, borderColor, QskGradient() ); + Q_D( QskArcRenderNode ); + + if ( rect.isEmpty() || arcMetrics.isNull() || !gradient.isVisible() ) + { + d->resetNode( this ); + return; + } + + const auto metrics = arcMetrics.toAbsolute( rect.size() ); + + if ( borderWidth >= 0.5 * metrics.thickness() ) + { + d->resetNode( this ); + return; + } + + const bool coloredGeometry = hasHint( PreferColoredGeometry ) + && QskArcRenderer::isGradientSupported( rect, metrics, gradient ); + + bool dirtyGeometry = d->updateMetrics( rect, metrics, radial, borderWidth ); + bool dirtyMaterial = d->updateColors( QColor(), gradient ); + + if ( coloredGeometry != isGeometryColored() ) + dirtyGeometry = dirtyMaterial = true; + + if ( dirtyGeometry || dirtyMaterial ) + { + if ( coloredGeometry ) + { + setColoring( QskFillNode::Polychrome ); + + QskArcRenderer::setColoredFillLines( rect, metrics, radial, + borderWidth, gradient, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + else + { + setColoring( rect, gradient ); + + if ( dirtyGeometry ) + { + QskArcRenderer::setFillLines( rect, metrics, + radial, borderWidth, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + } + } } -void QskArcRenderNode::updateNode( +void QskArcRenderNode::updateBorder( const QRectF& rect, + const QskArcMetrics& arcMetrics, bool radial, + qreal borderWidth, const QColor& borderColor ) +{ + Q_D( QskArcRenderNode ); + + if ( rect.isEmpty() || arcMetrics.isNull() + || !qskHasBorder( borderWidth, borderColor ) ) + { + d->resetNode( this ); + return; + } + + const bool coloredGeometry = hasHint( PreferColoredGeometry ); + + bool dirtyGeometry = d->updateMetrics( rect, arcMetrics, radial, borderWidth ); + bool dirtyMaterial = d->updateColors( borderColor, QskGradient() ); + + if ( coloredGeometry != isGeometryColored() ) + dirtyGeometry = dirtyMaterial = true; + + if ( dirtyGeometry || dirtyMaterial ) + { + const auto metrics = arcMetrics.toAbsolute( rect.size() ); + borderWidth = qMin( borderWidth, 0.5 * metrics.thickness() ); + + if ( coloredGeometry ) + { + setColoring( QskFillNode::Polychrome ); + + QskArcRenderer::setColoredBorderLines( rect, metrics, radial, + borderWidth, borderColor, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + else + { + setColoring( borderColor ); + + if ( dirtyGeometry ) + { + QskArcRenderer::setBorderLines( rect, metrics, radial, + borderWidth, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } + } + } +} + +void QskArcRenderNode::updateArc( const QRectF& rect, const QskArcMetrics& arcMetrics, bool radial, qreal borderWidth, const QColor& borderColor, const QskGradient& gradient ) { Q_D( QskArcRenderNode ); const auto metrics = arcMetrics.toAbsolute( rect.size() ); + if ( rect.isEmpty() || metrics.isNull() ) + { + d->resetNode( this ); + return; + } + const auto borderMax = 0.5 * metrics.thickness(); const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax ); - const bool hasBorder = ( borderWidth > 0.0 ) - && borderColor.isValid() && ( borderColor.alpha() > 0 ); + const bool hasBorder = qskHasBorder( borderWidth, borderColor ); - if ( rect.isEmpty() || metrics.isNull() || !( hasFill || hasBorder ) ) + if ( hasBorder && hasFill ) { - d->resetValues(); - QskSGNode::resetGeometry( this ); + /* + When having a monochrome gradient that has the same color as the + border we might be able to use QskFillNode::Monochrome. For the + moment this is not implemented + */ + borderWidth = qMin( borderWidth, borderMax ); - return; + const bool isDirty = d->updateMetrics( rect, arcMetrics, radial, borderWidth ) + || d->updateColors( borderColor, gradient ) || !isGeometryColored(); + + if ( isDirty ) + { + setColoring( QskFillNode::Polychrome ); + + QskArcRenderer::setColoredBorderAndFillLines( rect, metrics, radial, + borderWidth, borderColor, gradient, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + } } - - borderWidth = qMin( borderWidth, borderMax ); - - QskHashValue hash = 3496; - - hash = qHashBits( &rect, sizeof( QRectF ), hash ); - hash = qHash( borderWidth, hash ); - hash = qHashBits( &borderColor, sizeof( borderColor ), hash ); - hash = metrics.hash( hash ); - hash = gradient.hash( hash ); - hash = qHash( radial, hash ); - - if ( hash == d->hash ) - return; - - d->hash = hash; - - auto coloring = QskFillNode::Polychrome; - -#if 0 - if ( !( hasFill && hasBorder ) ) + else if ( hasBorder ) { - if ( hasBorder || ( hasFill && gradient.isMonochrome() ) ) - coloring = QskFillNode::Monochrome; + updateBorder( rect, arcMetrics, radial, borderWidth, borderColor ); } -#endif - - auto& geometry = *this->geometry(); - - if ( coloring == QskFillNode::Polychrome ) + else if ( hasFill ) { - setColoring( coloring ); - - QskArcRenderer::renderArc( rect, metrics, radial, - borderWidth, gradient, borderColor, geometry ); + updateFilling( rect, arcMetrics, radial, borderWidth, gradient ); } else { - if ( hasFill ) - { - setColoring( gradient.rgbStart() ); - - QskArcRenderer::renderArc( rect, metrics, radial, - borderWidth, gradient, borderColor, geometry ); - } - else - { - setColoring( borderColor ); - - QskArcRenderer::renderArc( rect, metrics, radial, - borderWidth, gradient, borderColor, geometry ); - } + d->resetNode( this ); + return; } - - markDirty( QSGNode::DirtyGeometry ); - markDirty( QSGNode::DirtyMaterial ); - - geometry.markVertexDataDirty(); } diff --git a/src/nodes/QskArcRenderNode.h b/src/nodes/QskArcRenderNode.h index f0d248ab..4ae4b2c1 100644 --- a/src/nodes/QskArcRenderNode.h +++ b/src/nodes/QskArcRenderNode.h @@ -22,12 +22,14 @@ class QSK_EXPORT QskArcRenderNode : public QskFillNode QskArcRenderNode(); ~QskArcRenderNode() override; - void updateNode( const QRectF&, const QskArcMetrics&, const QskGradient& ); + void updateFilling( const QRectF&, const QskArcMetrics&, const QskGradient& ); + void updateFilling( const QRectF&, const QskArcMetrics&, bool radial, + qreal borderWidth, const QskGradient& ); - void updateNode( const QRectF&, const QskArcMetrics&, + void updateBorder( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, const QColor& borderColor ); - void updateNode( const QRectF&, const QskArcMetrics&, bool radial, + void updateArc( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, const QColor& borderColor, const QskGradient& ); private: diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp index 1a7963ce..7569939d 100644 --- a/src/nodes/QskArcRenderer.cpp +++ b/src/nodes/QskArcRenderer.cpp @@ -11,7 +11,6 @@ #include "QskRgbValue.h" #include -#include static inline QskVertex::Line* qskAllocateLines( QSGGeometry& geometry, int lineCount ) @@ -466,8 +465,8 @@ bool QskArcRenderer::isGradientSupported( const QRectF& rect, if ( qskFuzzyCompare( direction.aspectRatio(), aspectRatio ) ) { /* - we should be able to create a list of stops from - this gradient that works for the renderer. TODO ... + we should be able to create a list of stops from + this gradient that works for the renderer. TODO ... */ } } @@ -483,17 +482,55 @@ bool QskArcRenderer::isGradientSupported( const QRectF& rect, return false; } -void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, - bool radial, const QskGradient& gradient, QSGGeometry& geometry ) -{ - renderArc( rect, metrics, radial, 0, gradient, QColor(), geometry ); -} - -void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, - bool radial, qreal borderWidth, const QskGradient& gradient, +void QskArcRenderer::setColoredBorderLines( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& geometry ) { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); + + if ( borderWidth <= 0.0 || !( borderColor.isValid() && borderColor.alpha() > 0 ) ) + { + qskAllocateColoredLines( geometry, 0 ); + return; + } + + const Renderer renderer( rect, metrics, radial, QskGradient(), borderColor ); + + if ( const auto lines = qskAllocateColoredLines( geometry, renderer.borderCount() ) ) + { + renderer.renderArc( metrics.thickness(), borderWidth, + static_cast< QskVertex::ColoredLine* >( nullptr ), lines ); + } +} + +void QskArcRenderer::setColoredFillLines( const QRectF& rect, const QskArcMetrics& metrics, + bool radial, qreal borderWidth, const QskGradient& gradient, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); + + if ( !gradient.isVisible() ) + { + qskAllocateColoredLines( geometry, 0 ); + return; + } + + const Renderer renderer( rect, metrics, radial, gradient, QColor( 0, 0, 0, 0 ) ); + + if ( const auto lines = qskAllocateColoredLines( geometry, renderer.fillCount() ) ) + { + renderer.renderArc( metrics.thickness(), borderWidth, lines, + static_cast< QskVertex::ColoredLine* >( nullptr ) ); + } +} + +void QskArcRenderer::setColoredBorderAndFillLines( const QRectF& rect, + const QskArcMetrics& metrics, bool radial, qreal borderWidth, + const QColor& borderColor, const QskGradient& gradient, QSGGeometry& geometry ) +{ + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); const Renderer renderer( rect, metrics, radial, gradient, borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) ); @@ -523,13 +560,20 @@ void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics } } -void QskArcRenderer::renderBorderGeometry( const QRectF& rect, +void QskArcRenderer::setBorderLines( const QRectF& rect, const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry ) { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); - + geometry.markVertexDataDirty(); + + if ( borderWidth <= 0.0 ) + { + qskAllocateLines( geometry, 0 ); + return; + } + const Renderer renderer( rect, metrics, radial, QskGradient(), 0 ); - + const auto lines = qskAllocateLines( geometry, renderer.borderCount() ); if ( lines ) { @@ -538,10 +582,11 @@ void QskArcRenderer::renderBorderGeometry( const QRectF& rect, } } -void QskArcRenderer::renderFillGeometry( const QRectF& rect, +void QskArcRenderer::setFillLines( const QRectF& rect, const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry ) { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); + geometry.markVertexDataDirty(); const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 ); diff --git a/src/nodes/QskArcRenderer.h b/src/nodes/QskArcRenderer.h index 1b0ce528..aa0e40f2 100644 --- a/src/nodes/QskArcRenderer.h +++ b/src/nodes/QskArcRenderer.h @@ -25,24 +25,32 @@ namespace QskArcRenderer - using shaders setting the colors */ - QSK_EXPORT void renderBorderGeometry( const QRectF&, + QSK_EXPORT void setBorderLines( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); - QSK_EXPORT void renderFillGeometry( const QRectF&, + QSK_EXPORT void setFillLines( const QRectF&, const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); /* Filling the geometry with color information: see QSGGeometry::defaultAttributes_ColoredPoint2D() + + Usually used in combination with QSGVertexColorMaterial */ QSK_EXPORT bool isGradientSupported( const QRectF&, const QskArcMetrics&, const QskGradient& ); - QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, - qreal borderWidth, const QskGradient&, const QColor& borderColor, QSGGeometry& ); + QSK_EXPORT void setColoredBorderLines( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, + const QColor& borderColor, QSGGeometry& ); - QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, + QSK_EXPORT void setColoredFillLines( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, const QskGradient&, QSGGeometry& ); + + QSK_EXPORT void setColoredBorderAndFillLines( const QRectF&, + const QskArcMetrics&, bool radial, qreal borderWidth, + const QColor& borderColor, const QskGradient&, QSGGeometry& ); } #endif