QskArcRenderer introduced

This commit is contained in:
Uwe Rathmann 2024-09-11 10:24:22 +02:00
parent cc64460a80
commit 68e9303357
10 changed files with 928 additions and 319 deletions

View File

@ -325,7 +325,7 @@ Skin::Palette Skin::palette( QskSkin::ColorScheme colorScheme ) const
Qt::white,
0xff4a4a4a,
0xff555555,
{ { { 0.0, 0xff991100 }, { 0.2, 0xff9a7a57 }, { 0.5, 0xff3726af }, { 1.0, Qt::black } } },
{ { { 0.0, 0xff991100 }, { 0.4, 0xff9a7a57 }, { 1.0, 0xff3726af } } },
0x10ffffff,
0xff222222
};
@ -342,8 +342,7 @@ Skin::Palette Skin::palette( QskSkin::ColorScheme colorScheme ) const
Qt::black,
0xffe5e5e5,
0xffc4c4c4,
{ { { 0.0, 0xffff3122 }, { 0.2, 0xfffeeeb7 }, { 0.3, 0xffa7b0ff }, { 0.5, 0xff6776ff },
{ 1.0, Qt::black } } },
{ { { 0.0, 0xffff3122 }, { 0.4, 0xfffeeeb7 }, { 0.6, 0xffa7b0ff }, { 1.0, 0xff6776ff } } },
0x10000000,
0xffdddddd
};

View File

@ -9,153 +9,10 @@
#include <QskIntervalF.h>
#include <QskArcMetrics.h>
#include <QskArcNode.h>
#include <QskArcRenderNode.h>
#include <qpainterpath.h>
#include <qmath.h>
#define PAINTED_NODE 0
#if PAINTED_NODE
/*
This is a fallback implementation for a user who is using an outdated
version of QSkinny where only shaders for linear gradients are available.
*/
#include <QskPaintedNode.h>
#include <qpainter.h>
#include <qpainterpath.h>
#include <qpen.h>
#include <qbrush.h>
namespace
{
QConicalGradient qskQConicalGradient(
const QskGradientStops& stops, qreal startAngle, qreal spanAngle )
{
QskGradientStops scaledStops;
scaledStops.reserve( stops.size() );
const auto ratio = qAbs( spanAngle ) / 360.0;
if ( spanAngle > 0.0 )
{
for ( auto it = stops.cbegin(); it != stops.cend(); ++it )
scaledStops += { ratio * it->position(), it->color() };
}
else
{
for ( auto it = stops.crbegin(); it != stops.crend(); ++it )
scaledStops += { 1.0 - ratio * it->position(), it->color() };
}
QConicalGradient qGradient( QPointF(), startAngle );
qGradient.setStops( qskToQGradientStops( scaledStops ) );
return qGradient;
}
class PaintedArcNode : public QskPaintedNode
{
public:
void setArcData( const QRectF&, const QskArcMetrics&,
qreal, const QColor&, const QskGradient&, QQuickWindow* );
protected:
void paint( QPainter*, const QSize&, const void* nodeData ) override;
QskHashValue hash( const void* nodeData ) const override;
private:
QskHashValue arcHash( const QRectF&, const QskArcMetrics&,
qreal, const QColor&, const QskGradient& ) const;
QBrush fillBrush( const QskGradient&, const QRectF&, qreal, qreal ) const;
struct ArcData
{
QPointF translation;
QPen pen;
QBrush brush;
QPainterPath path;
QskHashValue hash;
};
};
void PaintedArcNode::setArcData(
const QRectF& rect, const QskArcMetrics& metrics,
qreal borderWidth, const QColor& borderColor,
const QskGradient& gradient, QQuickWindow* window )
{
const auto hash = arcHash( rect, metrics, borderWidth, borderColor, gradient );
const auto brush = fillBrush( gradient, rect,
metrics.startAngle(), metrics.spanAngle() );
QPen pen( borderColor, borderWidth );
if ( borderWidth <= 0.0 )
pen.setStyle( Qt::NoPen );
const auto path = metrics.painterPath( rect );
const auto r = path.controlPointRect();
const ArcData arcData { r.topLeft(), pen, brush, path, hash };
update( window, r, QSizeF(), &arcData );
}
void PaintedArcNode::paint( QPainter* painter, const QSize&, const void* nodeData )
{
const auto arcData = reinterpret_cast< const ArcData* >( nodeData );
painter->setRenderHint( QPainter::Antialiasing, true );
painter->translate( -arcData->translation );
painter->setPen( arcData->pen );
painter->setBrush( arcData->brush );
painter->drawPath( arcData->path );
}
QskHashValue PaintedArcNode::hash( const void* nodeData ) const
{
const auto arcData = reinterpret_cast< const ArcData* >( nodeData );
return arcData->hash;
}
QBrush PaintedArcNode::fillBrush( const QskGradient& gradient,
const QRectF& rect, qreal startAngle, qreal spanAngle ) const
{
const auto qGradient = qskQConicalGradient(
gradient.stops(), startAngle, spanAngle );
const qreal sz = qMax( rect.width(), rect.height() );
const qreal sx = rect.width() / sz;
const qreal sy = rect.height() / sz;
QTransform t;
t.scale( sx, sy );
t.translate( rect.center().x() / sx, rect.center().y() / sy );
QBrush brush( qGradient );
brush.setTransform( t );
return brush;
}
inline QskHashValue PaintedArcNode::arcHash(
const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth,
const QColor& borderColor, const QskGradient& gradient ) const
{
auto hash = metrics.hash( 6753 );
hash = qHashBits( &rect, sizeof( rect ), hash );
hash = qHash( borderWidth, hash );
hash = qHash( borderColor.rgba(), hash );
hash = gradient.hash( hash );
return hash;
}
}
#endif // PAINTED_NODE
namespace
{
inline QskArcMetrics segmentMetrics(
@ -334,39 +191,16 @@ QSGNode* CircularChartSkinlet::updateSampleNode( const QskSkinnable* skinnable,
return nullptr;
}
QSGNode* CircularChartSkinlet::updateArcSegmentNode(
const QskSkinnable* skinnable,
QSGNode* CircularChartSkinlet::updateArcSegmentNode( const QskSkinnable*,
QSGNode* node, qreal borderWidth, const QColor& borderColor,
const QskGradient& gradient, const QskArcMetrics& metrics ) const
{
#if PAINTED_NODE
auto arcNode = static_cast< PaintedArcNode* >( node );
auto arcNode = static_cast< QskArcRenderNode* >( node );
if ( arcNode == nullptr )
arcNode = new PaintedArcNode();
arcNode = new QskArcRenderNode();
const auto chart = static_cast< const CircularChart* >( skinnable );
arcNode->setArcData( m_data->closedArcRect, metrics,
borderWidth, borderColor, gradient, chart->window() );
#else
Q_UNUSED( skinnable )
auto fillGradient = gradient;
if ( fillGradient.type() == QskGradient::Stops )
{
fillGradient.setStretchMode( QskGradient::StretchToSize );
fillGradient.setConicDirection( 0.5, 0.5,
metrics.startAngle(), metrics.spanAngle() );
}
auto arcNode = static_cast< QskArcNode* >( node );
if ( arcNode == nullptr )
arcNode = new QskArcNode();
arcNode->setArcData( m_data->closedArcRect, metrics,
borderWidth, borderColor, fillGradient );
#endif
arcNode->updateNode( m_data->closedArcRect, metrics, true,
borderWidth, borderColor, gradient );
return arcNode;
}

View File

@ -101,6 +101,8 @@ list(APPEND SOURCES
list(APPEND HEADERS
nodes/QskArcNode.h
nodes/QskArcRenderer.h
nodes/QskArcRenderNode.h
nodes/QskArcShadowNode.h
nodes/QskBasicLinesNode.h
nodes/QskBoxNode.h
@ -141,6 +143,8 @@ list(APPEND PRIVATE_HEADERS
list(APPEND SOURCES
nodes/QskArcNode.cpp
nodes/QskArcRenderer.cpp
nodes/QskArcRenderNode.cpp
nodes/QskArcShadowNode.cpp
nodes/QskBasicLinesNode.cpp
nodes/QskBoxNode.cpp

View File

@ -21,6 +21,83 @@ static void qskRegisterArcMetrics()
Q_CONSTRUCTOR_FUNCTION( qskRegisterArcMetrics )
static inline QPainterPath qskRadialPathPath(
const QRectF& rect, qreal startAngle, qreal spanAngle, qreal width )
{
const auto sz = qMin( rect.width(), rect.height() );
const auto tx = width * rect.width() / sz;
const auto ty = width * rect.height() / sz;
const auto innerRect = rect.adjusted( tx, ty, -tx, -ty );
QPainterPath path;
if ( innerRect.isEmpty() )
{
if ( qAbs( spanAngle ) >= 360.0 )
{
path.addEllipse( rect );
}
else
{
// pie
path.arcMoveTo( rect, startAngle );
path.arcTo( rect, startAngle, spanAngle );
path.lineTo( rect.center() );
path.closeSubpath();
}
}
else
{
if ( qAbs( spanAngle ) >= 360.0 )
{
path.addEllipse( rect );
QPainterPath innerPath;
innerPath.addEllipse( innerRect );
path -= innerPath;
}
else
{
/*
We need the end point of the inner arc to add the line that connects
the inner/outer arcs. As QPainterPath does not offer such a method
we insert a dummy arcMoveTo and grab the calculated position.
*/
path.arcMoveTo( innerRect, startAngle + spanAngle );
const auto pos = path.currentPosition();
path.arcMoveTo( rect, startAngle ); // replaces the dummy arcMoveTo above
path.arcTo( rect, startAngle, spanAngle );
path.lineTo( pos );
path.arcTo( innerRect, startAngle + spanAngle, -spanAngle );
path.closeSubpath();
}
}
return path;
}
static inline QPainterPath qskOrthogonalPath(
const QRectF& rect, qreal startAngle, qreal spanAngle, qreal width )
{
const auto t2 = 0.5 * width;
const auto r = rect.adjusted( t2, t2, -t2, -t2 );
QPainterPath arcPath;
arcPath.arcMoveTo( r, startAngle );
arcPath.arcTo( r, startAngle, spanAngle );
QPainterPathStroker stroker;
stroker.setCapStyle( Qt::FlatCap );
stroker.setWidth( width );
return stroker.createStroke( arcPath );
}
static inline qreal qskInterpolated( qreal from, qreal to, qreal ratio )
{
return from + ( to - from ) * ratio;
@ -122,66 +199,26 @@ QskArcMetrics QskArcMetrics::toAbsolute( qreal radius ) const noexcept
return QskArcMetrics( m_startAngle, m_spanAngle, t, Qt::AbsoluteSize );
}
QPainterPath QskArcMetrics::painterPath( const QRectF& ellipseRect ) const
QPainterPath QskArcMetrics::painterPath( const QRectF& rect, bool radial ) const
{
const auto sz = qMin( ellipseRect.width(), ellipseRect.height() );
qreal t = m_thickness;
if ( m_relativeSize )
t = qskEffectiveThickness( 0.5 * sz, t );
if ( t <= 0.0 || qFuzzyIsNull( m_spanAngle ) )
return QPainterPath();
const auto tx = t * ellipseRect.width() / sz;
const auto ty = t * ellipseRect.height() / sz;
const auto innerRect = ellipseRect.adjusted( tx, ty, -tx, -ty );
QPainterPath path;
if ( innerRect.isEmpty() )
if ( !qFuzzyIsNull( m_spanAngle ) )
{
if ( qAbs( m_spanAngle ) >= 360.0 )
qreal t = m_thickness;
if ( m_relativeSize )
{
path.addEllipse( ellipseRect );
const auto sz = qMin( rect.width(), rect.height() );
t = qskEffectiveThickness( 0.5 * sz, t );
}
else
if ( t > 0.0 )
{
// pie
path.arcMoveTo( ellipseRect, m_startAngle );
path.arcTo( ellipseRect, m_startAngle, m_spanAngle );
path.lineTo( ellipseRect.center() );
path.closeSubpath();
}
}
else
{
if ( qAbs( m_spanAngle ) >= 360.0 )
{
path.addEllipse( ellipseRect );
QPainterPath innerPath;
innerPath.addEllipse( innerRect );
path -= innerPath;
}
else
{
/*
We need the end point of the inner arc to add the line that connects
the inner/outer arcs. As QPainterPath does not offer such a method
we insert a dummy arcMoveTo and grab the calculated position.
*/
path.arcMoveTo( innerRect, m_startAngle + m_spanAngle );
const auto pos = path.currentPosition();
path.arcMoveTo( ellipseRect, m_startAngle ); // replaces the dummy arcMoveTo above
path.arcTo( ellipseRect, m_startAngle, m_spanAngle );
path.lineTo( pos );
path.arcTo( innerRect, m_startAngle + m_spanAngle, -m_spanAngle );
path.closeSubpath();
if ( radial )
path = qskRadialPathPath( rect, m_startAngle, m_spanAngle, t );
else
path = qskOrthogonalPath( rect, m_startAngle, m_spanAngle, t );
}
}

View File

@ -29,8 +29,8 @@ class QSK_EXPORT QskArcMetrics
constexpr QskArcMetrics( qreal thickness,
Qt::SizeMode = Qt::AbsoluteSize ) noexcept;
constexpr QskArcMetrics( qreal startAngle, qreal spanAngle,
qreal thickness, Qt::SizeMode = Qt::AbsoluteSize ) noexcept;
constexpr QskArcMetrics( qreal startAngle, qreal spanAngle, qreal thickness,
Qt::SizeMode = Qt::AbsoluteSize ) noexcept;
bool operator==( const QskArcMetrics& ) const noexcept;
bool operator!=( const QskArcMetrics& ) const noexcept;
@ -62,7 +62,22 @@ class QSK_EXPORT QskArcMetrics
QskArcMetrics toAbsolute( qreal radiusX, qreal radiusY ) const noexcept;
QskArcMetrics toAbsolute( qreal radius ) const noexcept;
QPainterPath painterPath( const QRectF& ellipseRect ) const;
/*
The arc is interpolated by pairs of points, where one point is on
the outer and the other on the inner side of the arc. The length between
these points depends on the thickness.
When radial is set the inner point lies on the line between the outer point
and the center of the arc. This corresponds to the lines of a conic gradient.
Otherwise the line between the inner and outer point is orthogonal to the
tangent at the point in the middle of the arc. This is how the width
of the pen is expanded by QPainter::drawArc.
Note, that the radial flag is irrelevant for circular arcs as the tangent
is always orthogonal to any point on the circle.
*/
QPainterPath painterPath( const QRectF& ellipseRect, bool radial = false ) const;
QRectF boundingRect( const QRectF& ellipseRect ) const;
QSizeF boundingSize( const QSizeF& ellipseSize ) const;

View File

@ -6,14 +6,14 @@
#include "QskArcNode.h"
#include "QskArcMetrics.h"
#include "QskArcShadowNode.h"
#include "QskArcRenderNode.h"
#include "QskArcRenderer.h"
#include "QskMargins.h"
#include "QskGradient.h"
#include "QskShapeNode.h"
#include "QskStrokeNode.h"
#include "QskSGNode.h"
#include "QskShadowMetrics.h"
#include <qpen.h>
#include <qpainterpath.h>
namespace
@ -21,8 +21,9 @@ namespace
enum NodeRole
{
ShadowRole,
FillRole,
BorderRole
PathRole,
ArcRole
};
}
@ -43,21 +44,18 @@ static inline QskGradient qskEffectiveGradient(
return gradient;
}
static inline QRectF qskEffectiveRect(
const QRectF& rect, const qreal borderWidth )
template< typename Node >
inline Node* qskInsertOrRemoveNode( QSGNode* parentNode, quint8 role, bool isValid )
{
if ( borderWidth <= 0.0 )
return rect;
using namespace QskSGNode;
return qskValidOrEmptyInnerRect( rect, QskMargins( 0.5 * borderWidth ) );
}
Node* oldNode = static_cast< Node* >( findChildNode( parentNode, role ) );
Node* newNode = isValid ? ensureNode< Node >( oldNode ) : nullptr;
static void qskUpdateChildren( QSGNode* parentNode, quint8 role, QSGNode* node )
{
static const QVector< quint8 > roles = { ShadowRole, FillRole, BorderRole };
static const QVector< quint8 > roles = { ShadowRole, PathRole, ArcRole };
replaceChildNode( roles, role, parentNode, oldNode, newNode );
auto oldNode = QskSGNode::findChildNode( parentNode, role );
QskSGNode::replaceChildNode( roles, role, parentNode, oldNode, node );
return newNode;
}
QskArcNode::QskArcNode()
@ -69,57 +67,44 @@ QskArcNode::~QskArcNode()
}
void QskArcNode::setArcData( const QRectF& rect,
const QskArcMetrics& arcMetrics, const QskGradient& fillGradient )
const QskArcMetrics& arcMetrics, const QskGradient& gradient )
{
setArcData( rect, arcMetrics, 0.0, QColor(), fillGradient, {}, {} );
setArcData( rect, arcMetrics, 0.0, QColor(), gradient, {}, {} );
}
void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics,
const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient )
const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient )
{
setArcData( rect, arcMetrics, borderWidth, borderColor, fillGradient, {}, {} );
setArcData( rect, arcMetrics, borderWidth, borderColor, gradient, {}, {} );
}
void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics,
const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient,
const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient,
const QColor& shadowColor, const QskShadowMetrics& shadowMetrics )
{
const bool radial = false;
const auto metricsArc = arcMetrics.toAbsolute( rect.size() );
const auto gradient = qskEffectiveGradient( fillGradient, metricsArc );
auto shadowNode = static_cast< QskArcShadowNode* >(
QskSGNode::findChildNode( this, ShadowRole ) );
auto fillNode = static_cast< QskShapeNode* >(
QskSGNode::findChildNode( this, FillRole ) );
auto borderNode = static_cast< QskStrokeNode* >(
QskSGNode::findChildNode( this, BorderRole ) );
const auto arcRect = qskEffectiveRect( rect, borderWidth );
if ( metricsArc.isNull() || arcRect.isEmpty() )
if ( metricsArc.isNull() || rect.isEmpty() )
{
delete shadowNode;
delete fillNode;
delete borderNode;
delete QskSGNode::findChildNode( this, ShadowRole );
delete QskSGNode::findChildNode( this, PathRole );
delete QskSGNode::findChildNode( this, ArcRole );
return;
}
const auto isFillNodeVisible = gradient.isVisible();
const auto isStrokeNodeVisible = ( borderWidth > 0.0 ) && ( borderColor.alpha() > 0 );
const auto isShadowNodeVisible = isFillNodeVisible &&
shadowColor.isValid() && ( shadowColor.alpha() > 0.0 );
const auto hasFilling = gradient.isVisible();
const auto hasBorder = ( borderWidth > 0.0 )
&& borderColor.isValid() && ( borderColor.alpha() > 0 );
const auto hasShadow = shadowColor.isValid() && ( shadowColor.alpha() > 0 );
const auto path = metricsArc.painterPath( arcRect );
auto shadowNode = qskInsertOrRemoveNode< QskArcShadowNode >(
this, ShadowRole, hasFilling && hasShadow );
if ( isShadowNodeVisible )
if ( shadowNode )
{
if ( shadowNode == nullptr )
{
shadowNode = new QskArcShadowNode;
QskSGNode::setNodeRole( shadowNode, ShadowRole );
}
/*
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
@ -127,55 +112,30 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
and not only to its radius. TODO ...
*/
const auto sm = shadowMetrics.toAbsolute( arcRect.size() );
const auto shadowRect = sm.shadowRect( arcRect );
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 );
}
else
auto pathNode = qskInsertOrRemoveNode< QskShapeNode >( this, PathRole,
hasFilling && !QskArcRenderer::isGradientSupported( rect, metricsArc, gradient ) );
if ( pathNode )
{
delete shadowNode;
shadowNode = nullptr;
const auto path = metricsArc.painterPath( rect, radial );
pathNode->updateNode( path, QTransform(), rect,
qskEffectiveGradient( gradient, metricsArc ) );
}
if ( isFillNodeVisible )
auto arcNode = qskInsertOrRemoveNode< QskArcRenderNode >(
this, ArcRole, hasBorder || ( hasFilling && !pathNode ) );
if ( arcNode )
{
if ( fillNode == nullptr )
{
fillNode = new QskShapeNode;
QskSGNode::setNodeRole( fillNode, FillRole );
}
fillNode->updateNode( path, QTransform(), arcRect, gradient );
arcNode->updateNode( rect, metricsArc, radial,
borderWidth, borderColor, pathNode ? QskGradient() : gradient );
}
else
{
delete fillNode;
fillNode = nullptr;
}
if ( isStrokeNodeVisible )
{
if ( borderNode == nullptr )
{
borderNode = new QskStrokeNode;
QskSGNode::setNodeRole( borderNode, BorderRole );
}
QPen pen( borderColor, borderWidth );
pen.setCapStyle( Qt::FlatCap );
borderNode->updateNode( path, QTransform(), pen );
}
else
{
delete borderNode;
borderNode = nullptr;
}
qskUpdateChildren( this, ShadowRole, shadowNode );
qskUpdateChildren( this, FillRole, fillNode );
qskUpdateChildren( this, BorderRole, borderNode );
}

View File

@ -0,0 +1,121 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskArcRenderNode.h"
#include "QskGradient.h"
#include "QskArcRenderer.h"
#include "QskArcMetrics.h"
#include "QskGradient.h"
#include "QskSGNode.h"
#include "QskFillNodePrivate.h"
class QskArcRenderNodePrivate final : public QskFillNodePrivate
{
public:
inline void resetValues() { hash = 0; }
QskHashValue hash = 0;
};
QskArcRenderNode::QskArcRenderNode()
: QskFillNode( *new QskArcRenderNodePrivate )
{
}
QskArcRenderNode::~QskArcRenderNode()
{
}
void QskArcRenderNode::updateNode( const QRectF& rect,
const QskArcMetrics& metrics, const QskGradient& gradient )
{
updateNode( rect, metrics, false, 0.0, QColor(), gradient );
}
void QskArcRenderNode::updateNode( const QRectF& rect,
const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor )
{
updateNode( rect, metrics, false, borderWidth, borderColor, QskGradient() );
}
void QskArcRenderNode::updateNode(
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() );
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 );
if ( rect.isEmpty() || metrics.isNull() || !( hasFill || hasBorder ) )
{
d->resetValues();
QskSGNode::resetGeometry( this );
return;
}
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 ) )
{
if ( hasBorder || ( hasFill && gradient.isMonochrome() ) )
coloring = QskFillNode::Monochrome;
}
#endif
auto& geometry = *this->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::DirtyMaterial );
geometry.markVertexDataDirty();
}

View File

@ -0,0 +1,37 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_ARC_RENDER_NODE_H
#define QSK_ARC_RENDER_NODE_H
#include "QskGlobal.h"
#include "QskFillNode.h"
class QskGradient;
class QskArcMetrics;
class QskArcRenderNodePrivate;
class QSK_EXPORT QskArcRenderNode : public QskFillNode
{
using Inherited = QskFillNode;
public:
QskArcRenderNode();
~QskArcRenderNode() override;
void updateNode( const QRectF&, const QskArcMetrics&, const QskGradient& );
void updateNode( const QRectF&, const QskArcMetrics&,
qreal borderWidth, const QColor& borderColor );
void updateNode( const QRectF&, const QskArcMetrics&, bool radial,
qreal borderWidth, const QColor& borderColor, const QskGradient& );
private:
Q_DECLARE_PRIVATE( QskArcRenderNode )
};
#endif

View File

@ -0,0 +1,554 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskArcRenderer.h"
#include "QskArcMetrics.h"
#include "QskGradient.h"
#include "QskVertex.h"
#include "QskBoxColorMap.h"
#include "QskRgbValue.h"
#include <qsggeometry.h>
#include <qdebug.h>
static inline QskVertex::Line* qskAllocateLines(
QSGGeometry& geometry, int lineCount )
{
geometry.allocate( 2 * lineCount ); // 2 points per line
return reinterpret_cast< QskVertex::Line* >( geometry.vertexData() );
}
static inline QskVertex::ColoredLine* qskAllocateColoredLines(
QSGGeometry& geometry, int lineCount )
{
geometry.allocate( 2 * lineCount ); // 2 points per line
return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() );
}
namespace
{
template< class Line >
class OrthogonalStroker
{
public:
OrthogonalStroker( const QRectF& rect, qreal thickness, qreal border )
: m_thickness( thickness )
, m_border( border )
, m_rx( 0.5 * ( rect.width() - m_thickness ) )
, m_ry( 0.5 * ( rect.height() - m_thickness ) )
, m_offsetToBorder( 0.5 * m_thickness - border )
, m_aspectRatio( m_rx / m_ry )
, m_cx( rect.x() + 0.5 * rect.width() )
, m_cy( rect.y() + 0.5 * rect.height() )
{
}
inline void setLinesAt( const qreal radians,
const QskVertex::Color fillColor, const QskVertex::Color borderColor,
Line* fill, Line* outerBorder, Line* innerBorder ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
const auto v = normalVector( cos, sin );
const QPointF p0( m_cx + m_rx * cos, m_cy - m_ry * sin );
const auto v1 = v * m_offsetToBorder;
const auto p1 = p0 + v1;
const auto p2 = p0 - v1;
if ( fill )
fill->setLine( p1, p2, fillColor );
if ( outerBorder )
{
const auto v2 = v * m_border;
outerBorder->setLine( p1 + v2, p1, borderColor );
innerBorder->setLine( p2 - v2, p2, borderColor );
}
}
inline void setClosingBorderLines( const Line& l,
Line* lines, qreal sign, const QskVertex::Color color ) const
{
const auto& pos = l.p1;
const auto& l0 = lines[0];
const auto dx = sign * l0.dy();
const auto dy = sign * l0.dx();
lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color );
lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color );
lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color );
}
private:
inline QPointF normalVector( const qreal cos, const qreal sin ) const
{
/*
The inner/outer points are found by shifting orthogonally along the
ellipse tangent:
m = w / h * tan( angle )
y = m * x;
x² + y² = 1.0
=> x = 1.0 / sqrt( 1.0 + m² );
Note: the angle of the orthogonal vector could
also be found ( first quadrant ) by:
atan2( tan( angle ), h / w );
Note: we return the vector mirrored vertically, so that it
matches the coordinate system used by Qt.
*/
if ( qFuzzyIsNull( cos ) )
return { 0.0, ( sin < 0.0 ) ? 1.0 : -1.0 };
const qreal m = m_aspectRatio * ( sin / cos );
const qreal t = 1.0 / qSqrt( 1.0 + m * m );
const auto dx = ( cos >= 0.0 ) ? t : -t;
return { dx, -m * dx };
}
const qreal m_thickness;
const qreal m_border;
// radii t the middle of the arc
const qreal m_rx, m_ry;
// distances between the middle and the beginning of the border
const qreal m_offsetToBorder;
const qreal m_aspectRatio; // m_rx / m_ry
// center
const qreal m_cx, m_cy;
};
template< class Line >
class RadialStroker
{
public:
RadialStroker( const QRectF& rect, qreal thickness, qreal border )
: m_sx( qMax( rect.width() / rect.height(), 1.0 ) )
, m_sy( qMax( rect.height() / rect.width(), 1.0 ) )
, m_rx1( 0.5 * rect.width() )
, m_ry1( 0.5 * rect.height() )
, m_rx2( m_rx1 - m_sx * border )
, m_ry2( m_ry1 - m_sy * border )
, m_rx3( m_rx1 - m_sx * ( thickness - border ) )
, m_ry3( m_ry1 - m_sy * ( thickness - border ) )
, m_rx4( m_rx1 - m_sx * thickness )
, m_ry4( m_ry1 - m_sy * thickness )
, m_center( rect.x() + m_rx1, rect.y() + m_ry1 )
{
}
inline void setLinesAt( const qreal radians,
const QskVertex::Color fillColor, const QskVertex::Color borderColor,
Line* fill, Line* outer, Line* inner ) const
{
const QPointF v( qFastCos( radians ), -qFastSin( radians ) );
const auto x1 = m_center.x() + m_rx2 * v.x();
const auto y1 = m_center.y() + m_ry2 * v.y();
const auto x2 = m_center.x() + m_rx3 * v.x();
const auto y2 = m_center.y() + m_ry3 * v.y();
if ( fill )
fill->setLine( x1, y1, x2, y2, fillColor );
if ( outer )
{
const auto x3 = m_center.x() + m_rx1 * v.x();
const auto y3 = m_center.y() + m_ry1 * v.y();
const auto x4 = m_center.x() + m_rx4 * v.x();
const auto y4 = m_center.y() + m_ry4 * v.y();
outer->setLine( x3, y3, x1, y1, borderColor );
inner->setLine( x4, y4, x2, y2, borderColor );
}
}
inline void setClosingBorderLines( const Line& l,
Line* lines, qreal sign, const QskVertex::Color color ) const
{
const auto& pos = l.p1;
// Good enough until it is decided if we want to keep the radial mode.
const auto& l0 = lines[0];
const auto s = m_sx / m_sy;
const auto dx = sign * l0.dy() * s;
const auto dy = sign * l0.dx() / s;
lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color );
lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color );
lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color );
}
private:
// stretch factors of the ellipse
const qreal m_sx, m_sy;
// radii: out->in
const qreal m_rx1, m_ry1, m_rx2, m_ry2, m_rx3, m_ry3, m_rx4, m_ry4;
// center point
const QPointF m_center;
};
template< class Line >
class CircularStroker
{
public:
CircularStroker( const QRectF& rect, qreal thickness, qreal border )
: m_center( rect.center() )
, m_radius( 0.5 * ( rect.width() - thickness ) )
, m_distOut( 0.5 * thickness )
, m_distIn( m_distOut - border )
{
}
inline void setLinesAt( const qreal radians,
const QskVertex::Color fillColor, const QskVertex::Color borderColor,
Line* fill, Line* outer, Line* inner ) const
{
const QPointF v( qFastCos( radians ), -qFastSin( radians ) );
const auto p0 = m_center + m_radius * v;
const auto dv1 = v * m_distIn;
const auto p1 = p0 + dv1;
const auto p2 = p0 - dv1;
if ( fill )
fill->setLine( p1, p2, fillColor );
if ( outer )
{
const auto dv2 = v * m_distOut;
const auto p3 = p0 + dv2;
const auto p4 = p0 - dv2;
outer->setLine( p3, p1, borderColor );
inner->setLine( p4, p2, borderColor );
}
}
inline void setClosingBorderLines( const Line& l,
Line* lines, qreal sign, const QskVertex::Color color ) const
{
const auto& pos = l.p1;
const auto& l0 = lines[0];
const auto dx = sign * l0.dy();
const auto dy = sign * l0.dx();
lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color );
lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color );
lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color );
}
private:
// center point
const QPointF m_center;
const qreal m_radius; // middle of the arc
// distances from the middle to the inner/outer side of the border
const qreal m_distOut, m_distIn;
};
}
namespace
{
class Renderer
{
public:
Renderer( const QRectF&, const QskArcMetrics&,
bool radial, const QskGradient&, const QskVertex::Color& );
int fillCount() const;
int borderCount() const;
template< class Line >
void renderArc( const qreal thickness, const qreal border, Line*, Line* ) const;
private:
int arcLineCount() const;
template< class LineStroker, class Line >
void renderLines( const LineStroker&, Line*, Line* ) const;
const QRectF& m_rect;
const qreal m_radians1;
const qreal m_radians2;
const bool m_radial; // for circular arcs radial/orthogonal does not differ
const bool m_closed;
const QskGradient& m_gradient;
const QskVertex::Color m_borderColor;
};
Renderer::Renderer( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, const QskGradient& gradient, const QskVertex::Color& borderColor )
: m_rect( rect )
, m_radians1( qDegreesToRadians( metrics.startAngle() ) )
, m_radians2( qDegreesToRadians( metrics.endAngle() ) )
, m_radial( radial )
, m_closed( metrics.isClosed() )
, m_gradient( gradient )
, m_borderColor( borderColor )
{
}
int Renderer::arcLineCount() const
{
// not very sophisticated - TODO ...
const auto radius = 0.5 * qMax( m_rect.width(), m_rect.height() );
const auto radians = qAbs( m_radians2 - m_radians1 );
const auto count = qCeil( ( radius * radians ) / 3.0 );
return qBound( 3, count, 160 );
}
int Renderer::fillCount() const
{
if ( !m_gradient.isVisible() )
return 0;
return arcLineCount() + m_gradient.stepCount() - 1;
}
template< class Line >
void Renderer::renderArc( const qreal thickness, const qreal border,
Line* fillLines, Line* borderLines ) const
{
if ( qskFuzzyCompare( m_rect.width(), m_rect.height() ) )
{
const CircularStroker< Line > stroker( m_rect, thickness, border );
renderLines( stroker, fillLines, borderLines );
}
else if ( m_radial )
{
const RadialStroker< Line > stroker( m_rect, thickness, border );
renderLines( stroker, fillLines, borderLines );
}
else
{
const OrthogonalStroker< Line > stroker( m_rect, thickness, border );
renderLines( stroker, fillLines, borderLines );
}
}
template< class LineStroker, class Line >
void Renderer::renderLines( const LineStroker& lineStroker,
Line* fillLines, Line* borderLines ) const
{
QskBoxRenderer::GradientIterator it;
if ( fillLines )
{
if ( m_gradient.stepCount() <= 1 )
{
it.reset( m_gradient.rgbStart(), m_gradient.rgbEnd() );
}
else
{
it.reset( m_gradient.stops() );
it.advance(); // the first stop is always covered by the contour
}
}
const auto count = arcLineCount();
const auto radiansSpan = m_radians2 - m_radians1;
const qreal stepMax = count - 1;
const auto stepSize = radiansSpan / stepMax;
auto l = fillLines;
auto outer = borderLines;
auto inner = borderLines;
if ( borderLines )
{
outer = borderLines;
if ( !m_closed )
outer += 3;
inner = outer + count;
if ( !m_closed )
inner += 3;
}
for ( int i = 0; i < count; i++ )
{
const auto progress = i / stepMax;
while( !it.isDone() && ( it.position() < progress ) )
{
const auto radians = m_radians1 + it.position() * radiansSpan;
lineStroker.setLinesAt( radians, it.color(), m_borderColor,
l++, nullptr, nullptr );
it.advance();
}
const auto radians = m_radians1 + i * stepSize;
const auto color = it.colorAt( progress );
lineStroker.setLinesAt( radians, color, m_borderColor,
l ? l++ : nullptr,
outer ? outer + i : nullptr,
inner ? inner + count - 1 - i : nullptr
);
}
if ( borderLines && !m_closed )
{
const auto sign = ( radiansSpan > 0.0 ) ? 1.0 : -1.0;
lineStroker.setClosingBorderLines( inner[count - 1], outer, sign, m_borderColor );
lineStroker.setClosingBorderLines( outer[count - 1], inner, sign, m_borderColor );
}
}
int Renderer::borderCount() const
{
if ( m_borderColor.a == 0 )
return 0;
auto count = 2 * arcLineCount();
if ( !m_closed )
count += 2 * 3;
return count;
}
}
bool QskArcRenderer::isGradientSupported( const QRectF& rect,
const QskArcMetrics& metrics, const QskGradient& gradient )
{
if ( rect.isEmpty() || metrics.isNull() )
return true;
if ( !gradient.isVisible() || gradient.isMonochrome() )
return true;
switch( gradient.type() )
{
case QskGradient::Stops:
{
return true;
}
case QskGradient::Conic:
{
const auto direction = gradient.conicDirection();
if ( direction.center() == rect.center() )
{
const auto aspectRatio = rect.width() / rect.height();
if ( qskFuzzyCompare( direction.aspectRatio(), aspectRatio ) )
{
/*
we should be able to create a list of stops from
this gradient that works for the renderer. TODO ...
*/
}
}
return false;
}
default:
{
return false;
}
}
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,
const QColor& borderColor, QSGGeometry& geometry )
{
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
const Renderer renderer( rect, metrics, radial, gradient,
borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) );
const auto borderCount = renderer.borderCount();
const auto fillCount = renderer.fillCount();
auto lineCount = borderCount + fillCount;
if ( borderCount && fillCount )
lineCount++; // connecting line
const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines )
{
const auto fillLines = fillCount ? lines : nullptr;
const auto borderLines = borderCount ? lines + lineCount - borderCount : nullptr;
renderer.renderArc( metrics.thickness(), borderWidth, fillLines, borderLines );
if ( fillCount && borderCount )
{
const auto idx = fillCount;
lines[idx].p1 = lines[idx - 1].p2;
lines[idx].p2 = lines[idx + 1].p1;
}
}
}
void QskArcRenderer::renderBorderGeometry( const QRectF& rect,
const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry )
{
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
const Renderer renderer( rect, metrics, radial, QskGradient(), 0 );
const auto lines = qskAllocateLines( geometry, renderer.borderCount() );
if ( lines )
{
QskVertex::Line* fill = nullptr;
renderer.renderArc( metrics.thickness(), borderWidth, fill, lines );
}
}
void QskArcRenderer::renderFillGeometry( const QRectF& rect,
const QskArcMetrics& metrics, bool radial, qreal borderWidth, QSGGeometry& geometry )
{
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 );
const auto lines = qskAllocateLines( geometry, renderer.fillCount() );
if ( lines )
{
QskVertex::Line* border = nullptr;
renderer.renderArc( metrics.thickness(), borderWidth, lines, border );
}
}

View File

@ -0,0 +1,48 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_ARC_RENDERER_H
#define QSK_ARC_RENDERER_H
#include "QskGlobal.h"
class QskArcMetrics;
class QskGradient;
class QSGGeometry;
class QRectF;
class QColor;
namespace QskArcRenderer
{
/*
Filling the geometry without any color information:
see QSGGeometry::defaultAttributes_Point2D()
- clip nodes
- using shaders setting the colors
*/
QSK_EXPORT void renderBorderGeometry( const QRectF&,
const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& );
QSK_EXPORT void renderFillGeometry( const QRectF&,
const QskArcMetrics&, bool radial, qreal borderWidth, QSGGeometry& );
/*
Filling the geometry with color information:
see QSGGeometry::defaultAttributes_ColoredPoint2D()
*/
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 renderArc( const QRectF&, const QskArcMetrics&, bool radial,
const QskGradient&, QSGGeometry& );
}
#endif