diff --git a/playground/shadows/ArcPage.cpp b/playground/shadows/ArcPage.cpp index 3ed5c5e4..1d30c168 100644 --- a/playground/shadows/ArcPage.cpp +++ b/playground/shadows/ArcPage.cpp @@ -87,16 +87,18 @@ ArcPage::ArcPage( QQuickItem* parent ) arc->setSpanAngle( 270.0 ); arc->setThickness( 10.0 ); - arc->setFillColor( Qt::darkRed ); + arc->setFillColor( Qt::yellow ); - arc->setBorderWidth( 0 ); - arc->setBorderColor( Qt::darkYellow ); + arc->setBorderWidth( 2.0 ); + arc->setBorderColor( Qt::darkBlue ); +#if 0 arc->setShadowColor( Qt::black ); arc->setSpreadRadius( 0.0 ); arc->setBlurRadius( 4.0 ); arc->setOffsetX( 2.0 ); arc->setOffsetY( 2.0 ); +#endif } auto panel = new ControlPanel( arc ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4674eb23..0b774c94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -102,6 +102,7 @@ list(APPEND SOURCES list(APPEND HEADERS nodes/QskArcNode.h nodes/QskArcRenderer.h + nodes/QskArcRenderNode.h nodes/QskArcShadowNode.h nodes/QskBasicLinesNode.h nodes/QskBoxNode.h @@ -143,6 +144,7 @@ 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 diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index e3de5c0a..a3fd4042 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -6,6 +6,7 @@ #include "QskArcNode.h" #include "QskArcMetrics.h" #include "QskArcShadowNode.h" +#include "QskArcRenderNode.h" #include "QskMargins.h" #include "QskGradient.h" #include "QskShapeNode.h" @@ -13,9 +14,18 @@ #include "QskSGNode.h" #include "QskShadowMetrics.h" -#include #include +#define ARC_RENDERER + +#ifdef ARC_RENDERER + using BorderNode = QskArcRenderNode; +#else + #include + using BorderNode = QskStrokeNode; +#endif + + namespace { enum NodeRole @@ -107,7 +117,7 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics auto fillNode = static_cast< QskShapeNode* >( QskSGNode::findChildNode( this, FillRole ) ); - auto borderNode = static_cast< QskStrokeNode* >( + auto borderNode = static_cast< BorderNode* >( QskSGNode::findChildNode( this, BorderRole ) ); const auto arcRect = qskEffectiveRect( rect, borderWidth ); @@ -173,14 +183,19 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics { if ( borderNode == nullptr ) { - borderNode = new QskStrokeNode; + borderNode = new BorderNode; QskSGNode::setNodeRole( borderNode, BorderRole ); } +#ifdef ARC_RENDERER + borderNode->updateNode( arcRect, metricsArc, borderWidth, + borderColor, gradient ); +#else QPen pen( borderColor, borderWidth ); pen.setCapStyle( Qt::FlatCap ); borderNode->updateNode( path, QTransform(), pen ); +#endif } else { @@ -188,7 +203,7 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics borderNode = nullptr; } - qskUpdateChildren(this, ShadowRole, shadowNode); - qskUpdateChildren(this, FillRole, fillNode); - qskUpdateChildren(this, BorderRole, borderNode); + qskUpdateChildren( this, ShadowRole, shadowNode ); + qskUpdateChildren( this, FillRole, fillNode ); + qskUpdateChildren( this, BorderRole, borderNode ); } diff --git a/src/nodes/QskArcRenderNode.cpp b/src/nodes/QskArcRenderNode.cpp new file mode 100644 index 00000000..cfe9b4ef --- /dev/null +++ b/src/nodes/QskArcRenderNode.cpp @@ -0,0 +1,95 @@ +/****************************************************************************** + * 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" + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +#if 1 +#include +#include +// deriving from QskFillNode:TODO ... +Q_GLOBAL_STATIC( QSGVertexColorMaterial, qskMaterialColorVertex ) +#endif + +class QskArcRenderNodePrivate final : public QSGGeometryNodePrivate +{ + public: + QskArcRenderNodePrivate() + : geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 ) + { + } + + inline void resetValues() + { + hash = 0; + } + + QSGGeometry geometry; + QskHashValue hash = 0; +}; + +QskArcRenderNode::QskArcRenderNode() + : QSGGeometryNode( *new QskArcRenderNodePrivate ) +{ + Q_D( QskArcRenderNode ); + + setGeometry( &d->geometry ); + + setMaterial( qskMaterialColorVertex ); + setFlag( QSGNode::OwnsMaterial, false ); +} + +void QskArcRenderNode::updateNode( + const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth, + const QColor& borderColor, const QskGradient& gradient ) +{ + Q_D( QskArcRenderNode ); + + bool visible = !( rect.isEmpty() || metrics.isNull() ); + if ( visible ) + { + visible = gradient.isVisible(); + if ( !visible ) + { + visible = ( borderWidth > 0.0 ) + && borderColor.isValid() && ( borderColor.alpha() > 0 ); + } + } + + if ( !visible ) + { + d->resetValues(); + QskSGNode::resetGeometry( this ); + + return; + } + + QskHashValue hash = 3496; + + hash = qHashBits( &rect, sizeof( QRectF ), hash ); + hash = qHash( borderWidth, hash ); + hash = qHash( borderColor.rgba(), hash ); + hash = metrics.hash( hash ); + hash = gradient.hash( hash ); + + if ( hash != d->hash ) + { + d->hash = hash; + + QskArcRenderer::renderBorderGeometry( + rect, metrics, borderWidth, *geometry() ); + + markDirty( QSGNode::DirtyGeometry ); + markDirty( QSGNode::DirtyMaterial ); + } +} diff --git a/src/nodes/QskArcRenderNode.h b/src/nodes/QskArcRenderNode.h new file mode 100644 index 00000000..c47879a0 --- /dev/null +++ b/src/nodes/QskArcRenderNode.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * 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 + +class QskGradient; +class QskArcMetrics; + +class QskArcRenderNodePrivate; + +class QSK_EXPORT QskArcRenderNode : public QSGGeometryNode +{ + using Inherited = QSGGeometryNode; + + public: + QskArcRenderNode(); + + void updateNode( const QRectF&, const QskArcMetrics&, qreal borderWidth, + const QColor& borderColor, const QskGradient& ); + + private: + Q_DECLARE_PRIVATE( QskArcRenderNode ) +}; + +#endif diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp index 9ff20905..4c35df5b 100644 --- a/src/nodes/QskArcRenderer.cpp +++ b/src/nodes/QskArcRenderer.cpp @@ -10,6 +10,10 @@ #include +#if 1 +#include +#endif + static inline QskVertex::Line* qskAllocateLines( QSGGeometry& geometry, int lineCount ) { @@ -44,8 +48,75 @@ static inline int qskApproximatedCircumference( const QRectF& rect ) static inline int qskStepCount( const QRectF& rect ) { +#if 0 + const auto dist = 3.0; +#else + const auto dist = 20.0; +#endif + const int length = qskApproximatedCircumference( rect ); - return std::max( 3, qCeil( length / 3.0 ) ); + return std::max( 3, qCeil( length / dist ) ); +} + +namespace +{ + class AngleIterator + { + public: + AngleIterator( qreal radians1, qreal radians2, int stepCount ); + + inline void operator++() { increment(); } + void increment(); + + inline double cos() const { return m_cos; } + inline double sin() const { return m_sin; } + + inline int step() const { return m_stepIndex; } + inline int stepCount() const { return m_stepCount; } + inline bool isDone() const { return m_stepIndex > m_stepCount; } + + private: + double m_cos; + double m_sin; + + int m_stepIndex; + + int m_stepCount; + + const double m_radians1; + const double m_radians2; + const double m_radiansStep; + }; + + inline AngleIterator::AngleIterator( qreal radians1, qreal radians2, int stepCount ) + : m_stepIndex( 0 ) + , m_stepCount( stepCount ) + , m_radians1( radians1 ) + , m_radians2( radians2 ) + , m_radiansStep( ( radians2 - radians1 ) / stepCount ) + { + m_cos = qFastCos( radians1 ); + m_sin = qFastSin( radians1 ); + } + + inline void AngleIterator::increment() + { + if ( ++m_stepIndex >= m_stepCount ) + { + if ( m_stepIndex == m_stepCount ) + { + m_cos = qFastCos( m_radians2 ); + m_sin = qFastSin( m_radians2 ); + } + } + else + { + const auto radians = m_radians1 + m_stepIndex * m_radiansStep; + + m_cos = qFastCos( radians ); + m_sin = qFastSin( radians ); + } + } } namespace @@ -58,49 +129,106 @@ namespace int fillCount() const; int borderCount() const; - void setBorderLines( QskVertex::Line* ) const; + int setBorderLines( QskVertex::ColoredLine* ) const; private: + int arcLineCount() const; + + void setArcLines( QskVertex::ColoredLine*, int lineCount, + const QPointF&, const qreal width, const qreal height, + const qreal radians1, const qreal radians2, + qreal arcWidth, const QskVertex::Color ) const; + const QRectF& m_rect; const QskArcMetrics& m_metrics; const qreal m_borderWidth; }; -} -Stroker::Stroker( const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth ) - : m_rect( rect ) - , m_metrics( metrics ) - , m_borderWidth( borderWidth ) -{ - Q_ASSERT( metrics.sizeMode() == Qt::AbsoluteSize ); -} - -int Stroker::fillCount() const -{ - return 0; // TODO -} - -int Stroker::borderCount() const -{ - if ( m_metrics.isNull() ) - return 0; - - int n = qskStepCount( m_rect ); - if ( !m_metrics.isClosed() ) + Stroker::Stroker( const QRectF& rect, + const QskArcMetrics& metrics, qreal borderWidth ) + : m_rect( rect ) + , m_metrics( metrics ) + , m_borderWidth( borderWidth ) { - n = qCeil( n * qAbs( m_metrics.spanAngle() ) / 360.0 ); - n += 1; // closing line + Q_ASSERT( metrics.sizeMode() == Qt::AbsoluteSize ); } - n *= 2; // inner/outer border - n += 1; // dummy line connection inner/outer border + int Stroker::fillCount() const + { + return 0; // TODO + } - return n; -} + int Stroker::arcLineCount() const + { + if ( m_metrics.isNull() ) + return 0; -void Stroker::setBorderLines( QskVertex::Line* lines ) const -{ - Q_UNUSED( lines ); + int n = qskStepCount( m_rect ); + if ( !m_metrics.isClosed() ) + n = qCeil( n * qAbs( m_metrics.spanAngle() ) / 360.0 ); + + return n; + } + + int Stroker::borderCount() const + { + if ( m_metrics.isNull() ) + return 0; + + return 2 * arcLineCount() + 1; + } + + void Stroker::setArcLines( QskVertex::ColoredLine* lines, int lineCount, + const QPointF& center, const qreal w, const qreal h, + const qreal radians1, const qreal radians2, + qreal arcWidth, const QskVertex::Color color ) const + { + const auto w2 = w - arcWidth; + const auto h2 = h - arcWidth; + + auto l = lines; + + for ( AngleIterator it( radians1, radians2, lineCount - 1 ); !it.isDone(); ++it ) + { + const auto x1 = center.x() + w * it.cos(); + const auto x2 = center.x() + w2 * it.cos(); + + const auto y1 = center.y() + h * it.sin(); + const auto y2 = center.y() + h2 * it.sin(); + + l++->setLine( x1, y1, x2, y2, color ); + } + + if ( l - lines != lineCount ) + qWarning() << lineCount << "->" << l - lines; + Q_ASSERT( l - lines == lineCount ); + } + + int Stroker::setBorderLines( QskVertex::ColoredLine* lines ) const + { + const auto center = m_rect.center(); + + const qreal radians1 = qDegreesToRadians( m_metrics.startAngle() ); + const qreal radians2 = qDegreesToRadians( m_metrics.endAngle() ); + + const int n = arcLineCount(); + + const QskVertex::Color color( QColor( Qt::darkBlue ) ); + + auto w = 0.5 * m_rect.width(); + auto h = 0.5 * m_rect.height(); + + setArcLines( lines, n, center, w, h, radians1, radians2, m_borderWidth, color ); + + w -= m_metrics.thickness() - m_borderWidth; + h -= m_metrics.thickness() - m_borderWidth; + + setArcLines( lines + n + 1, n, center, w, h, radians2, radians1, m_borderWidth, color ); + + lines[n] = { lines[n - 1].p2, lines[n + 1].p1 }; + + return 2 * n + 1; + } } void QskArcRenderer::renderBorderGeometry( const QRectF& rect, @@ -110,9 +238,17 @@ void QskArcRenderer::renderBorderGeometry( const QRectF& rect, Stroker stroker( rect, metrics, borderWidth ); - const auto lines = qskAllocateLines( geometry, stroker.borderCount() ); + const auto lineCount = stroker.borderCount(); + + const auto lines = qskAllocateColoredLines( geometry, lineCount ); if ( lines ) - stroker.setBorderLines( lines ); + { + const auto effectiveCount = stroker.setBorderLines( lines ); + if ( lineCount != effectiveCount ) + { + qWarning() << lineCount << effectiveCount; + } + } } void QskArcRenderer::renderFillGeometry( const QRectF& rect,