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.
This commit is contained in:
Uwe Rathmann 2023-04-12 12:19:26 +02:00
parent 76248e480b
commit 45a1bc3564
6 changed files with 100 additions and 89 deletions

View File

@ -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,
};
}

View File

@ -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;
}

View File

@ -4,18 +4,67 @@
*****************************************************************************/
#include "QskArcNode.h"
#include "QskArcMetrics.h"
#include "QskArcRenderer.h"
#include "QskArcMetrics.h"
#include "QskGradient.h"
#include "QskGradientDirection.h"
namespace
#include <qpainterpath.h>
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 );
}

View File

@ -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

View File

@ -5,50 +5,25 @@
#include "QskArcRenderer.h"
#include "QskArcMetrics.h"
#include "QskGradient.h"
#include "QskGradientDirection.h"
#include <qpainter.h>
#include <qpainterpath.h>
#include <qrect.h>
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 );
}

View File

@ -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