QskArcRenderer introduced
This commit is contained in:
parent
cc64460a80
commit
68e9303357
@ -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
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
}
|
||||
|
121
src/nodes/QskArcRenderNode.cpp
Normal file
121
src/nodes/QskArcRenderNode.cpp
Normal 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();
|
||||
}
|
37
src/nodes/QskArcRenderNode.h
Normal file
37
src/nodes/QskArcRenderNode.h
Normal 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
|
554
src/nodes/QskArcRenderer.cpp
Normal file
554
src/nodes/QskArcRenderer.cpp
Normal 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 );
|
||||
}
|
||||
}
|
48
src/nodes/QskArcRenderer.h
Normal file
48
src/nodes/QskArcRenderer.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user