From 45a1bc3564fb558a533755dabc47fcefe58e843a Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 12 Apr 2023 12:19:26 +0200 Subject: [PATCH] QskArcNode is a QskShapeNode now. The performance of the previous implementation was simply horrible, when drawing an arc with a small span angle. The size of the corresponding full circle is huge and the previous implementation always created an image/texture with that size. However the final implementation is supposed to create vertex lists - like what the box renderer does. So this code will also not stay forever. --- examples/iotdashboard/Skin.cpp | 2 +- src/controls/QskSkinlet.cpp | 12 ++-- src/nodes/QskArcNode.cpp | 105 ++++++++++++++++++++++++--------- src/nodes/QskArcNode.h | 13 +--- src/nodes/QskArcRenderer.cpp | 51 ++++------------ src/nodes/QskArcRenderer.h | 6 +- 6 files changed, 100 insertions(+), 89 deletions(-) diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index 7c0a845f..370eb36d 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -326,7 +326,7 @@ Skin::Palette DaytimeSkin::palette() const 0xffc4c4c4, { { { 0.0, 0xffff3122 }, { 0.2, 0xfffeeeb7 }, { 0.3, 0xffa7b0ff }, { 0.5, 0xff6776ff }, { 1.0, Qt::black } } }, - { { { 0.0, 0xffc4c4c4 }, { 0.5, 0xfff8f8f8 }, { 1.0, 0xffc4c4c4 } } }, + { { { 0.0, 0xffe0e0e0 }, { 0.5, 0xfff8f8f8 }, { 1.0, 0xffe0e0e0 } } }, 0xffdddddd, }; } diff --git a/src/controls/QskSkinlet.cpp b/src/controls/QskSkinlet.cpp index dd361fad..0b35c590 100644 --- a/src/controls/QskSkinlet.cpp +++ b/src/controls/QskSkinlet.cpp @@ -204,16 +204,15 @@ static inline QSGNode* qskUpdateBoxNode( } static inline QSGNode* qskUpdateArcNode( - const QskSkinnable* skinnable, QSGNode* node, const QRectF& rect, + const QskSkinnable*, QSGNode* node, const QRectF& rect, const QskGradient& fillGradient, const QskArcMetrics& metrics ) { - const auto control = skinnable->owningControl(); - if ( control == nullptr || rect.isEmpty() ) + if ( rect.isEmpty() ) return nullptr; - auto absoluteMetrics = metrics.toAbsolute( rect.size() ); + const auto absoluteMetrics = metrics.toAbsolute( rect.size() ); - if ( !qskIsArcVisible( metrics, fillGradient ) ) + if ( !qskIsArcVisible( absoluteMetrics, fillGradient ) ) return nullptr; auto arcNode = static_cast< QskArcNode* >( node ); @@ -221,8 +220,7 @@ static inline QSGNode* qskUpdateArcNode( if ( arcNode == nullptr ) arcNode = new QskArcNode(); - const auto r = qskSceneAlignedRect( control, rect ); - arcNode->setArcData( r, absoluteMetrics, fillGradient, control->window() ); + arcNode->setArcData( rect, absoluteMetrics, fillGradient ); return arcNode; } diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index 8cff0c36..a9259ed9 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -4,18 +4,67 @@ *****************************************************************************/ #include "QskArcNode.h" -#include "QskArcMetrics.h" #include "QskArcRenderer.h" +#include "QskArcMetrics.h" #include "QskGradient.h" +#include "QskGradientDirection.h" -namespace +#include + +static inline QskGradient effectiveGradient( const QRectF& rect, + const QskArcMetrics& metrics, const QskGradient& gradient ) { - class ArcData + if ( gradient.isMonochrome() ) + return gradient; + + bool isRadial = false; + + if ( gradient.type() == QskGradient::Linear ) { - public: - const QskArcMetrics& metrics; - const QskGradient& gradient; - }; + /* + Horizontal is interpreted as conic ( in direction of the arc ), + while Vertical means radial ( inner to outer border ) + */ + isRadial = gradient.linearDirection().isVertical(); + } + + auto g = gradient; + g.setStretchMode( QskGradient::NoStretch ); + + const auto center = rect.center(); + + if( isRadial ) + { + g.setRadialDirection( center.x(), center.y(), + rect.width(), rect.height() ); + + { + /* + Trying to do what QGradient::LogicalMode does in + the previous QPainter based implementation + */ + + const auto radius = 0.5 * qMin( rect.width(), rect.height() ); + const auto t = metrics.thickness() / radius; + + QskGradientStops stops; + stops.reserve( gradient.stops().size() ); + + for ( const auto& stop : gradient.stops() ) + { + const auto pos = 0.5 - t * ( 0.75 - stop.position() ); + stops += QskGradientStop( pos, stop.color() ); + } + + g.setStops( stops ); + } + } + else + { + g.setConicDirection( center.x(), center.y(), metrics.startAngle() ); + } + + return g; } QskArcNode::QskArcNode() @@ -26,27 +75,25 @@ QskArcNode::~QskArcNode() { } -void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& metrics, - const QskGradient& gradient, QQuickWindow* window ) +void QskArcNode::setArcData( const QRectF& rect, + const QskArcMetrics& metrics, const QskGradient& gradient ) { - const ArcData arcData { metrics, gradient }; - update( window, rect, QSizeF(), &arcData ); -} - -void QskArcNode::paint( QPainter* painter, const QSize& size, const void* nodeData ) -{ - const auto arcData = reinterpret_cast< const ArcData* >( nodeData ); - - const qreal t = arcData->metrics.thickness(); - const QRectF rect( 0.5 * t, 0.5 * t, size.width() - t, size.height() - t ); - - QskArcRenderer::renderArc( rect, arcData->metrics, arcData->gradient, painter ); -} - -QskHashValue QskArcNode::hash( const void* nodeData ) const -{ - const auto arcData = reinterpret_cast< const ArcData* >( nodeData ); - - auto h = arcData->metrics.hash(); - return arcData->gradient.hash( h ); +#if 1 + /* + Translating linear gradients into conic or radial gradients. + This code is a leftover from situations, where only linear + gradients had been available. Once the iotdashboard example + has been adjusted we will remove this code TODO ... + */ + const auto g = effectiveGradient( rect, metrics, gradient ); +#endif + + /* + For the moment using a QPainterPath/QskShapeNode. + But we can do better by creatig vertex lists manually + like what is done by the box renderer. TODO ... + */ + + const auto path = QskArcRenderer::arcPath( rect, metrics ); + updateNode( path, QTransform(), rect, g ); } diff --git a/src/nodes/QskArcNode.h b/src/nodes/QskArcNode.h index 083737f8..feb7f341 100644 --- a/src/nodes/QskArcNode.h +++ b/src/nodes/QskArcNode.h @@ -6,25 +6,18 @@ #ifndef QSK_ARC_NODE_H #define QSK_ARC_NODE_H -#include "QskPaintedNode.h" +#include "QskShapeNode.h" class QskArcMetrics; class QskGradient; -// should be a QSGGeometryNode, TODO .. - -class QSK_EXPORT QskArcNode : public QskPaintedNode +class QSK_EXPORT QskArcNode : public QskShapeNode { public: QskArcNode(); ~QskArcNode() override; - void setArcData( const QRectF&, const QskArcMetrics&, - const QskGradient&, QQuickWindow* ); - - protected: - void paint( QPainter*, const QSize&, const void* nodeData ) override; - QskHashValue hash( const void* nodeData ) const override; + void setArcData( const QRectF&, const QskArcMetrics&, const QskGradient& ); }; #endif diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp index 0b16eec9..88f61c23 100644 --- a/src/nodes/QskArcRenderer.cpp +++ b/src/nodes/QskArcRenderer.cpp @@ -5,50 +5,25 @@ #include "QskArcRenderer.h" #include "QskArcMetrics.h" -#include "QskGradient.h" -#include "QskGradientDirection.h" -#include +#include #include -void QskArcRenderer::renderArc(const QRectF& rect, - const QskArcMetrics& metrics, const QskGradient& gradient, QPainter* painter ) +QPainterPath QskArcRenderer::arcPath( + const QRectF& rect, const QskArcMetrics& metrics ) { - bool isRadial = false; + const auto m = metrics.toAbsolute( rect.size() ); - if ( gradient.type() == QskGradient::Linear ) - { - /* - Horizontal is interpreted as conic ( in direction of the arc ), - while Vertical means radial ( inner to outer border ) - */ - isRadial = gradient.linearDirection().isVertical(); - } + const qreal t2 = 0.5 * m.thickness(); + const auto r = rect.adjusted( t2, t2, -t2, -t2 ); - QBrush brush; + QPainterPath path; + path.arcMoveTo( r, m.startAngle() ); + path.arcTo( r, m.startAngle(), m.spanAngle() ); - const auto qStops = qskToQGradientStops( gradient.stops() ); + QPainterPathStroker stroker; + stroker.setWidth( m.thickness() ); + stroker.setCapStyle( Qt::FlatCap ); - if( isRadial ) - { - QRadialGradient radial( rect.center(), qMin( rect.width(), rect.height() ) ); - radial.setStops( qStops ); - - brush = radial; - } - else - { - QConicalGradient conical( rect.center(), metrics.startAngle() ); - conical.setStops( qStops ); - - brush = conical; - } - - painter->setRenderHint( QPainter::Antialiasing, true ); - painter->setPen( QPen( brush, metrics.thickness(), Qt::SolidLine, Qt::FlatCap ) ); - - const int startAngle = qRound( metrics.startAngle() * 16 ); - const int spanAngle = qRound( metrics.spanAngle() * 16 ); - - painter->drawArc( rect, startAngle, spanAngle ); + return stroker.createStroke( path ); } diff --git a/src/nodes/QskArcRenderer.h b/src/nodes/QskArcRenderer.h index 2ce8587d..f54b0061 100644 --- a/src/nodes/QskArcRenderer.h +++ b/src/nodes/QskArcRenderer.h @@ -9,15 +9,13 @@ #include "QskGlobal.h" class QskArcMetrics; -class QskGradient; -class QPainter; +class QPainterPath; class QRectF; namespace QskArcRenderer { - QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, - const QskGradient&, QPainter* ); + QSK_EXPORT QPainterPath arcPath( const QRectF&, const QskArcMetrics& ); }; #endif