working on arcs

This commit is contained in:
Uwe Rathmann 2023-04-14 09:47:10 +02:00
parent 7f2fbc7bd0
commit 73dd618626
7 changed files with 163 additions and 72 deletions

View File

@ -25,13 +25,6 @@ static inline qreal qskInterpolated( qreal from, qreal to, qreal ratio )
return from + ( to - from ) * ratio;
}
static inline qreal qskAbsoluted( qreal length, qreal percentage )
{
// 100% means -> 0.5 of length
percentage = qBound( 0.0, percentage, 100.0 );
return percentage / 100.0 * 0.5 * length;
}
void QskArcMetrics::setThickness( qreal thickness ) noexcept
{
m_thickness = thickness;
@ -93,32 +86,33 @@ QVariant QskArcMetrics::interpolate(
return QVariant::fromValue( from.interpolated( to, progress ) );
}
QskArcMetrics QskArcMetrics::toAbsolute( const QSizeF& size ) const noexcept
QskArcMetrics QskArcMetrics::toAbsolute( qreal radiusX, qreal radiusY ) const noexcept
{
if ( size.width() < 0.0 )
return toAbsolute( size.height() );
if ( radiusX < 0.0 )
return toAbsolute( radiusY );
if ( size.height() < 0.0 )
return toAbsolute( size.width() );
if ( radiusY < 0.0 )
return toAbsolute( radiusX );
return toAbsolute( qMin( size.width(), size.height() ) );
return toAbsolute( qMin( radiusX, radiusY ) );
}
QskArcMetrics QskArcMetrics::toAbsolute( qreal size ) const noexcept
QskArcMetrics QskArcMetrics::toAbsolute( qreal radius ) const noexcept
{
if ( m_sizeMode != Qt::RelativeSize )
return *this;
QskArcMetrics absoluted = *this;
QskArcMetrics m = *this;
if ( size <= 0.0 )
absoluted.m_thickness = 0.0;
else
absoluted.m_thickness = qskAbsoluted( size, absoluted.m_thickness );
if ( radius < 0.0 )
radius = 0.0;
absoluted.m_sizeMode = Qt::AbsoluteSize;
const auto ratio = qBound( 0.0, m.m_thickness, 100.0 ) / 100.0;
return absoluted;
m.m_thickness = radius * ratio;
m.m_sizeMode = Qt::AbsoluteSize;
return m;
}
QskHashValue QskArcMetrics::hash( QskHashValue seed ) const noexcept

View File

@ -56,8 +56,8 @@ class QSK_EXPORT QskArcMetrics
QskArcMetrics interpolated( const QskArcMetrics&,
qreal value ) const noexcept;
QskArcMetrics toAbsolute( const QSizeF& ) const noexcept;
QskArcMetrics toAbsolute( qreal ) const noexcept;
QskArcMetrics toAbsolute( qreal radiusX, qreal radiusY ) const noexcept;
QskArcMetrics toAbsolute( qreal radius ) const noexcept;
QskHashValue hash( QskHashValue seed = 0 ) const noexcept;

View File

@ -210,7 +210,10 @@ static inline QSGNode* qskUpdateArcNode(
if ( rect.isEmpty() )
return nullptr;
const auto absoluteMetrics = metrics.toAbsolute( rect.size() );
const auto rx = 0.5 * rect.width();
const auto ry = 0.5 * rect.height();
const auto absoluteMetrics = metrics.toAbsolute( rx, ry );
if ( !qskIsArcVisible( absoluteMetrics, fillGradient ) )
return nullptr;

View File

@ -11,31 +11,29 @@
#include <qpainterpath.h>
static inline QskGradient effectiveGradient( const QRectF& rect,
const QskArcMetrics& metrics, const QskGradient& gradient )
#define LINEAR_GRADIENT_HACK 1
#if LINEAR_GRADIENT_HACK
static inline QskGradient buildGradient( QskGradient::Type type,
const QRectF& rect, const QskArcMetrics& metrics,
const QskGradientStops& stops )
{
if ( gradient.isMonochrome() )
return gradient;
bool isRadial = false;
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();
}
auto g = gradient;
g.setStretchMode( QskGradient::NoStretch );
const auto center = rect.center();
if( isRadial )
QskGradient gradient;
gradient.setStretchMode( QskGradient::NoStretch );
if ( type == QskGradient::Conic )
{
g.setRadialDirection( center.x(), center.y(),
gradient.setConicDirection(
center.x(), center.y(), metrics.startAngle() );
gradient.setStops( stops );
}
else
{
gradient.setRadialDirection( center.x(), center.y(),
rect.width(), rect.height() );
{
@ -47,24 +45,58 @@ static inline QskGradient effectiveGradient( const QRectF& rect,
const auto radius = 0.5 * qMin( rect.width(), rect.height() );
const auto t = metrics.thickness() / radius;
QskGradientStops stops;
stops.reserve( gradient.stops().size() );
QskGradientStops scaledStops;
scaledStops.reserve( stops.size() );
for ( const auto& stop : gradient.stops() )
for ( const auto& stop : stops )
{
const auto pos = 0.5 - t * ( 0.75 - stop.position() );
stops += QskGradientStop( pos, stop.color() );
scaledStops += QskGradientStop( pos, stop.color() );
}
g.setStops( stops );
gradient.setStops( scaledStops );
}
}
else
return gradient;
}
#endif
static inline QskGradient effectiveGradient( const QRectF& rect,
const QskArcMetrics& metrics, const QskGradient& gradient )
{
if ( !gradient.isMonochrome() )
{
g.setConicDirection( center.x(), center.y(), metrics.startAngle() );
if ( gradient.type() == QskGradient::Stops )
{
const QskConicDirection dir(
rect.center(), metrics.startAngle() );
#if 0
dir.setSpanAngle( metrics.spanAngle() ); // what is "expected" ??
#endif
QskGradient g( gradient.stops() );
g.setStretchMode( QskGradient::NoStretch );
g.setConicDirection( dir );
return g;
}
#if LINEAR_GRADIENT_HACK
if ( gradient.type() == QskGradient::Linear )
{
// to keep the iotdashboard working: to be removed
const auto type = gradient.linearDirection().isHorizontal()
? QskGradient::Conic : QskGradient::Radial;
return buildGradient( type, rect, metrics, gradient.stops() );
}
#endif
}
return g;
return gradient;
}
QskArcNode::QskArcNode()
@ -78,22 +110,8 @@ QskArcNode::~QskArcNode()
void QskArcNode::setArcData( const QRectF& rect,
const QskArcMetrics& metrics, const QskGradient& gradient )
{
#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 );
updateNode( path, QTransform(), rect,
effectiveGradient( rect, metrics, gradient ) );
}

View File

@ -11,6 +11,11 @@
class QskArcMetrics;
class QskGradient;
/*
For the moment a QPainterPath/QskShapeNode.
But we can do better by creatig vertex lists manually
like what is done by the box renderer. TODO ...
*/
class QSK_EXPORT QskArcNode : public QskShapeNode
{
public:

View File

@ -9,10 +9,13 @@
#include <qpainterpath.h>
#include <qrect.h>
QPainterPath QskArcRenderer::arcPath(
static inline QPainterPath qskArcPath(
const QRectF& rect, const QskArcMetrics& metrics )
{
const auto m = metrics.toAbsolute( rect.size() );
const auto rx = 0.5 * rect.width();
const auto ry = 0.5 * rect.height();
const auto m = metrics.toAbsolute( rx, ry );
const qreal t2 = 0.5 * m.thickness();
const auto r = rect.adjusted( t2, t2, -t2, -t2 );
@ -27,3 +30,59 @@ QPainterPath QskArcRenderer::arcPath(
return stroker.createStroke( path );
}
static inline QRectF qskArcRect(
const QRectF& rect, const QskArcMetrics& metrics )
{
return qskArcPath( rect, metrics ).controlPointRect();
}
QPainterPath QskArcRenderer::arcPath(
qreal radius, const QskArcMetrics& metrics )
{
const QRectF r( 0.0, 0.0, 2 * radius, 2 * radius );
return qskArcPath( r, metrics );
}
QPainterPath QskArcRenderer::arcPath(
const QSizeF& diameters, const QskArcMetrics& metrics )
{
const QRectF r( 0.0, 0.0, diameters.width(), diameters.height() );
return qskArcPath( r, metrics );
}
QPainterPath QskArcRenderer::arcPath(
const QRectF& rect, const QskArcMetrics& metrics )
{
return qskArcPath( rect, metrics );
}
QRectF QskArcRenderer::arcRect( qreal radius, const QskArcMetrics& metrics )
{
const qreal d = 2.0 * radius;
return qskArcRect( QRectF( 0.0, 0.0, d, d ), metrics );
}
QRectF QskArcRenderer::arcRect( const QSizeF& diameters, const QskArcMetrics& metrics )
{
const QRectF r( 0.0, 0.0, diameters.width(), diameters.height() );
return qskArcRect( r, metrics );
}
QRectF QskArcRenderer::arcRect( const QRectF& rect, const QskArcMetrics& metrics )
{
return qskArcRect( rect, metrics );
}
QSizeF QskArcRenderer::arcSize(
const QSizeF& diameters, const QskArcMetrics& metrics )
{
if ( qFuzzyIsNull( metrics.spanAngle() ) )
return QSizeF();
if ( qAbs( metrics.spanAngle() ) >= 360.0 )
return diameters;
const QRectF r( 0.0, 0.0, diameters.width(), diameters.height() );
return qskArcRect( r, metrics ).size();
}

View File

@ -12,10 +12,22 @@ class QskArcMetrics;
class QPainterPath;
class QRectF;
class QSizeF;
namespace QskArcRenderer
{
// radius
QSK_EXPORT QPainterPath arcPath( qreal radius, const QskArcMetrics& );
QSK_EXPORT QRectF arcRect( qreal radius, const QskArcMetrics& );
// diameter
QSK_EXPORT QPainterPath arcPath( const QSizeF&, const QskArcMetrics& );
QSK_EXPORT QSizeF arcSize( const QSizeF&, const QskArcMetrics& );
QSK_EXPORT QRectF arcRect( const QSizeF&, const QskArcMetrics& );
// bounding rectangle
QSK_EXPORT QPainterPath arcPath( const QRectF&, const QskArcMetrics& );
QSK_EXPORT QRectF arcRect( const QRectF&, const QskArcMetrics& );
};
#endif