handling of arcs improved

This commit is contained in:
Uwe Rathmann 2024-09-23 16:04:09 +02:00
parent c9f7bf59ee
commit b69f84e865
6 changed files with 344 additions and 161 deletions

View File

@ -199,7 +199,7 @@ QSGNode* CircularChartSkinlet::updateArcSegmentNode( const QskSkinnable*,
if ( arcNode == nullptr ) if ( arcNode == nullptr )
arcNode = new QskArcRenderNode(); arcNode = new QskArcRenderNode();
arcNode->updateNode( m_data->closedArcRect, metrics, true, arcNode->updateArc( m_data->closedArcRect, metrics, true,
borderWidth, borderColor, gradient ); borderWidth, borderColor, gradient );
return arcNode; return arcNode;

View File

@ -10,7 +10,6 @@
#include "QskArcRenderer.h" #include "QskArcRenderer.h"
#include "QskMargins.h" #include "QskMargins.h"
#include "QskGradient.h" #include "QskGradient.h"
#include "QskShapeNode.h"
#include "QskSGNode.h" #include "QskSGNode.h"
#include "QskShadowMetrics.h" #include "QskShadowMetrics.h"
@ -22,40 +21,38 @@ namespace
{ {
ShadowRole, 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( static void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node )
const QskGradient& gradient, const QskArcMetrics& metrics )
{ {
if ( !gradient.isMonochrome() ) static const QVector< quint8 > roles = { ShadowRole, ArcRole, FillRole };
{
if ( gradient.type() == QskGradient::Stops )
{
QskGradient g( gradient.stops() );
g.setConicDirection( 0.5, 0.5, metrics.startAngle(), 360.0 );
return g; auto oldNode = QskSGNode::findChildNode( parentNode, role );
} QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node );
}
return gradient;
} }
template< typename Node > template< typename Node >
inline Node* qskInsertOrRemoveNode( QSGNode* parentNode, quint8 role, bool isValid ) inline Node* qskNode( QSGNode* parentNode, quint8 role )
{ {
using namespace QskSGNode; using namespace QskSGNode;
Node* oldNode = static_cast< Node* >( findChildNode( parentNode, role ) ); auto node = static_cast< Node* > ( findChildNode( parentNode, role ) );
Node* newNode = isValid ? ensureNode< Node >( oldNode ) : nullptr;
static const QVector< quint8 > roles = { ShadowRole, PathRole, ArcRole }; if ( node == nullptr )
replaceChildNode( roles, role, parentNode, oldNode, newNode ); {
node = new Node();
setNodeRole( node, role );
}
return newNode; return node;
} }
QskArcNode::QskArcNode() QskArcNode::QskArcNode()
@ -82,28 +79,24 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient, const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient,
const QColor& shadowColor, const QskShadowMetrics& shadowMetrics ) 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 ); const bool radial = false;
delete QskSGNode::findChildNode( this, PathRole ); const auto metricsArc = arcMetrics.toAbsolute( rect.size() );
delete QskSGNode::findChildNode( this, ArcRole );
return;
}
const auto hasFilling = gradient.isVisible(); const auto hasFilling = gradient.isVisible();
const auto hasBorder = ( borderWidth > 0.0 ) const auto hasBorder = ( borderWidth > 0.0 )
&& borderColor.isValid() && ( borderColor.alpha() > 0 ); && borderColor.isValid() && ( borderColor.alpha() > 0 );
const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 ); const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 );
auto shadowNode = qskInsertOrRemoveNode< QskArcShadowNode >( if ( hasShadow && hasFilling )
this, ShadowRole, hasFilling && hasShadow );
if ( shadowNode )
{ {
/* /*
The shader of the shadow node is for circular arcs and we have some The shader of the shadow node is for circular arcs and we have some
@ -112,6 +105,8 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
and not only to its radius. TODO ... and not only to its radius. TODO ...
*/ */
shadowNode = qskNode< QskArcShadowNode >( this, ShadowRole );
const auto sm = shadowMetrics.toAbsolute( rect.size() ); const auto sm = shadowMetrics.toAbsolute( rect.size() );
const auto shadowRect = sm.shadowRect( rect ); const auto shadowRect = sm.shadowRect( rect );
const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness(); const auto spreadRadius = sm.spreadRadius() + 0.5 * metricsArc.thickness();
@ -120,22 +115,32 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor ); metricsArc.startAngle(), metricsArc.spanAngle(), shadowColor );
} }
auto pathNode = qskInsertOrRemoveNode< QskShapeNode >( this, PathRole, if ( hasBorder || hasFilling )
hasFilling && !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) );
if ( pathNode )
{ {
const auto path = metricsArc.painterPath( rect, radial ); arcNode = qskNode< QskArcRenderNode >( this, ArcRole );
pathNode->updateNode( path, QTransform(), rect,
qskEffectiveGradient( gradient, metricsArc ) );
}
auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >( if ( hasBorder && hasFilling )
this, ArcRole, hasBorder || ( hasFilling && !pathNode ) );
if ( arcNode )
{ {
arcNode->updateNode( rect, metricsArc, radial, if ( !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) )
borderWidth, borderColor, pathNode ? QskGradient() : 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 );
} }
} }
}
qskUpdateChildren( this, ShadowRole, shadowNode );
qskUpdateChildren( this, ArcRole, arcNode );
qskUpdateChildren( this, FillRole, fillNode );
}

View File

@ -11,12 +11,61 @@
#include "QskSGNode.h" #include "QskSGNode.h"
#include "QskFillNodePrivate.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 class QskArcRenderNodePrivate final : public QskFillNodePrivate
{ {
public: 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() 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 ) 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, void QskArcRenderNode::updateFilling( const QRectF& rect,
const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor ) 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;
} }
void QskArcRenderNode::updateNode( 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::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, const QRectF& rect, const QskArcMetrics& arcMetrics, bool radial,
qreal borderWidth, const QColor& borderColor, const QskGradient& gradient ) qreal borderWidth, const QColor& borderColor, const QskGradient& gradient )
{ {
Q_D( QskArcRenderNode ); Q_D( QskArcRenderNode );
const auto metrics = arcMetrics.toAbsolute( rect.size() ); const auto metrics = arcMetrics.toAbsolute( rect.size() );
if ( rect.isEmpty() || metrics.isNull() )
{
d->resetNode( this );
return;
}
const auto borderMax = 0.5 * metrics.thickness(); const auto borderMax = 0.5 * metrics.thickness();
const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax ); const bool hasFill = gradient.isVisible() && ( borderWidth < borderMax );
const bool hasBorder = ( borderWidth > 0.0 ) const bool hasBorder = qskHasBorder( borderWidth, borderColor );
&& borderColor.isValid() && ( borderColor.alpha() > 0 );
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
return; moment this is not implemented
} */
borderWidth = qMin( borderWidth, borderMax ); borderWidth = qMin( borderWidth, borderMax );
QskHashValue hash = 3496; const bool isDirty = d->updateMetrics( rect, arcMetrics, radial, borderWidth )
|| d->updateColors( borderColor, gradient ) || !isGeometryColored();
hash = qHashBits( &rect, sizeof( QRectF ), hash ); if ( isDirty )
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 ) )
{ {
if ( hasBorder || ( hasFill && gradient.isMonochrome() ) ) setColoring( QskFillNode::Polychrome );
coloring = QskFillNode::Monochrome;
}
#endif
auto& geometry = *this->geometry(); QskArcRenderer::setColoredBorderAndFillLines( rect, metrics, radial,
borderWidth, borderColor, gradient, *geometry() );
if ( coloring == QskFillNode::Polychrome )
{
setColoring( coloring );
QskArcRenderer::renderArc( rect, metrics, radial,
borderWidth, gradient, borderColor, geometry );
}
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 );
}
}
markDirty( QSGNode::DirtyGeometry ); markDirty( QSGNode::DirtyGeometry );
markDirty( QSGNode::DirtyMaterial ); }
}
geometry.markVertexDataDirty(); else if ( hasBorder )
{
updateBorder( rect, arcMetrics, radial, borderWidth, borderColor );
}
else if ( hasFill )
{
updateFilling( rect, arcMetrics, radial, borderWidth, gradient );
}
else
{
d->resetNode( this );
return;
}
} }

View File

@ -22,12 +22,14 @@ class QSK_EXPORT QskArcRenderNode : public QskFillNode
QskArcRenderNode(); QskArcRenderNode();
~QskArcRenderNode() override; ~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 ); 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& ); qreal borderWidth, const QColor& borderColor, const QskGradient& );
private: private:

View File

@ -11,7 +11,6 @@
#include "QskRgbValue.h" #include "QskRgbValue.h"
#include <qsggeometry.h> #include <qsggeometry.h>
#include <qdebug.h>
static inline QskVertex::Line* qskAllocateLines( static inline QskVertex::Line* qskAllocateLines(
QSGGeometry& geometry, int lineCount ) QSGGeometry& geometry, int lineCount )
@ -483,17 +482,55 @@ bool QskArcRenderer::isGradientSupported( const QRectF& rect,
return false; return false;
} }
void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, void QskArcRenderer::setColoredBorderLines( const QRectF& rect,
bool radial, const QskGradient& gradient, QSGGeometry& geometry ) const QskArcMetrics& metrics, bool radial, qreal borderWidth,
{
renderArc( rect, metrics, radial, 0, gradient, QColor(), geometry );
}
void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, qreal borderWidth, const QskGradient& gradient,
const QColor& borderColor, QSGGeometry& geometry ) const QColor& borderColor, QSGGeometry& geometry )
{ {
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); 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, const Renderer renderer( rect, metrics, radial, gradient,
borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) ); borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) );
@ -523,10 +560,17 @@ 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 ) const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry )
{ {
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
geometry.markVertexDataDirty();
if ( borderWidth <= 0.0 )
{
qskAllocateLines( geometry, 0 );
return;
}
const Renderer renderer( rect, metrics, radial, QskGradient(), 0 ); const Renderer renderer( rect, metrics, radial, QskGradient(), 0 );
@ -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 ) const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry )
{ {
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
geometry.markVertexDataDirty();
const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 ); const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 );

View File

@ -25,24 +25,32 @@ namespace QskArcRenderer
- using shaders setting the colors - using shaders setting the colors
*/ */
QSK_EXPORT void renderBorderGeometry( const QRectF&, QSK_EXPORT void setBorderLines( const QRectF&,
const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& ); 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& ); const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& );
/* /*
Filling the geometry with color information: Filling the geometry with color information:
see QSGGeometry::defaultAttributes_ColoredPoint2D() see QSGGeometry::defaultAttributes_ColoredPoint2D()
Usually used in combination with QSGVertexColorMaterial
*/ */
QSK_EXPORT bool isGradientSupported( QSK_EXPORT bool isGradientSupported(
const QRectF&, const QskArcMetrics&, const QskGradient& ); const QRectF&, const QskArcMetrics&, const QskGradient& );
QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, bool radial, QSK_EXPORT void setColoredBorderLines( const QRectF&,
qreal borderWidth, const QskGradient&, const QColor& borderColor, QSGGeometry& ); 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& ); const QskGradient&, QSGGeometry& );
QSK_EXPORT void setColoredBorderAndFillLines( const QRectF&,
const QskArcMetrics&, bool radial, qreal borderWidth,
const QColor& borderColor, const QskGradient&, QSGGeometry& );
} }
#endif #endif