From f798f2228cc2fb2e381d9b147cb904c281da1f3b Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Thu, 29 Sep 2022 16:50:46 +0200 Subject: [PATCH] more details around shapes --- playground/shapes/ShapeItem.cpp | 134 +++++++++++++++++++++++--------- playground/shapes/ShapeItem.h | 2 + playground/shapes/main.cpp | 29 ++++++- src/nodes/QskShapeNode.cpp | 18 +++-- src/nodes/QskShapeNode.h | 4 +- 5 files changed, 138 insertions(+), 49 deletions(-) diff --git a/playground/shapes/ShapeItem.cpp b/playground/shapes/ShapeItem.cpp index 3e100e45..a316cb6f 100644 --- a/playground/shapes/ShapeItem.cpp +++ b/playground/shapes/ShapeItem.cpp @@ -9,6 +9,38 @@ #include #include +static inline QTransform transformForRects( const QRectF& r1, const QRectF& r2 ) +{ + return QTransform::fromTranslate( -r1.x(), -r1.y() ) + * QTransform::fromScale( r2.width() / r1.width(), r2.height() / r1.height() ) + * QTransform::fromTranslate( r2.x(), r2.y() ); +} + +static inline bool isVisible( const QColor& color ) +{ + return color.isValid() && ( color.alpha() > 0 ); +} + +static inline bool isVisible( const QPen& pen ) +{ + return ( pen.style() != Qt::NoPen ) && isVisible( pen.color() ); +} + +static inline QPen cosmeticPen( const QPen& pen, const QSizeF& size1, const QSizeF& size2 ) +{ + if ( pen.isCosmetic() || pen.widthF() <= 0.0 || size2.isEmpty() ) + return pen; + + const auto f = qMin( size1.width() / size2.width(), + size1.height() / size2.height() ); + + auto newPen = pen; + newPen.setWidthF( pen.widthF() * f ); + newPen.setCosmetic( true ); + + return newPen; +} + ShapeItem::ShapeItem( QQuickItem* parent ) : QskControl( parent ) { @@ -44,6 +76,16 @@ void ShapeItem::setGradient( const QColor& c1, const QColor& c2 ) } } +void ShapeItem::setGradient( QGradient::Preset preset ) +{ + const auto stops = QGradient( preset ).stops(); + if ( !stops.isEmpty() ) + { + // gradients with more than 2 clors do not work TODO ... + setGradient( stops.first().second, stops.last().second ); + } +} + void ShapeItem::setPath( const QPainterPath& path ) { if ( path != m_path ) @@ -67,28 +109,43 @@ void ShapeItem::updateNode( QSGNode* parentNode ) }; const auto rect = contentsRect(); - - /* - The triangulators in the nodes are able to do transformations - on the fly. TODO ... - */ - const auto path = scaledPath( rect ); + const auto pathRect = m_path.controlPointRect(); auto fillNode = static_cast< QskShapeNode* >( QskSGNode::findChildNode( parentNode, FillRole ) ); - if ( path.isEmpty() || rect.isEmpty() ) + auto borderNode = static_cast< QskStrokeNode* >( + QskSGNode::findChildNode( parentNode, BorderRole ) ); + + if ( rect.isEmpty() || pathRect.isEmpty() ) { delete fillNode; + delete borderNode; + + return; } - else + + const auto pen = ::cosmeticPen( m_pen, rect.size(), pathRect.size() ); + + if ( ::isVisible( m_fillColor[0] ) || ::isVisible( m_fillColor[1] ) ) { if ( fillNode == nullptr ) { fillNode = new QskShapeNode; QskSGNode::setNodeRole( fillNode, FillRole ); + + parentNode->prependChildNode( fillNode ); } + auto fillRect = rect; + if ( pen.style() != Qt::NoPen ) + { + const auto pw2 = 0.5 * pen.widthF(); + fillRect.adjust( pw2, pw2, -pw2, -pw2 ); + } + + const auto transform = ::transformForRects( pathRect, fillRect ); + if ( m_fillColor[0] != m_fillColor[1] ) { QLinearGradient gradient; @@ -97,51 +154,52 @@ void ShapeItem::updateNode( QSGNode* parentNode ) gradient.setColorAt( 0.0, m_fillColor[0] ); gradient.setColorAt( 1.0, m_fillColor[1] ); - fillNode->updateNode( path, &gradient ); + fillNode->updateNode( m_path, transform, &gradient ); } else { - fillNode->updateNode( path, m_fillColor[0] ); + fillNode->updateNode( m_path, transform, m_fillColor[0] ); } - - if ( fillNode->parent() != parentNode ) - parentNode->prependChildNode( fillNode ); - } - - auto borderNode = static_cast< QskStrokeNode* >( - QskSGNode::findChildNode( parentNode, BorderRole ) ); - - if ( path.isEmpty() || rect.isEmpty() ) - { - delete borderNode; } else { + delete fillNode; + } + + if ( ::isVisible( pen ) ) + { + if ( pen.widthF() > 1.0 ) + { + if ( !( pen.isSolid() && pen.color().alpha() == 255 ) ) + { + /* + We might end up with overlapping parts + at corners with angles < 180° + + What about translating the stroke into + a path ( QPainterPathStroker ) and using + a QskShapeNode then. TODO ... + */ + } + } + if ( borderNode == nullptr ) { borderNode = new QskStrokeNode; QskSGNode::setNodeRole( borderNode, BorderRole ); + + parentNode->appendChildNode( borderNode ); } - borderNode->updateNode( path, m_pen ); + const auto transform = ::transformForRects( pathRect, rect ); - if ( borderNode->parent() != parentNode ) - parentNode->appendChildNode( borderNode ); + const auto scaledPath = transform.map( m_path ); + borderNode->updateNode( scaledPath, pen ); + } + else + { + delete borderNode; } } -QPainterPath ShapeItem::scaledPath( const QRectF& rect ) const -{ - // does not center properly. TODO - const auto pw = 2 * m_pen.width(); - - const auto pathRect = m_path.controlPointRect(); - const auto r = rect.adjusted( pw, pw, -pw, -pw ); - - auto transform = QTransform::fromTranslate( r.left(), r.top() ); - transform.scale( r.width() / pathRect.width(), r.height() / pathRect.height() ); - - return transform.map( m_path ); -} - #include "moc_ShapeItem.cpp" diff --git a/playground/shapes/ShapeItem.h b/playground/shapes/ShapeItem.h index a2dbb63d..661a64d7 100644 --- a/playground/shapes/ShapeItem.h +++ b/playground/shapes/ShapeItem.h @@ -8,6 +8,7 @@ #include #include #include +#include class ShapeItem : public QskControl { @@ -21,6 +22,7 @@ class ShapeItem : public QskControl QPen pen() const; void setGradient( const QColor&, const QColor& ); + void setGradient( QGradient::Preset ); void setPath( const QPainterPath& ); QPainterPath path() const; diff --git a/playground/shapes/main.cpp b/playground/shapes/main.cpp index 8293e3b9..c40bf0d7 100644 --- a/playground/shapes/main.cpp +++ b/playground/shapes/main.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -18,6 +19,29 @@ namespace { return SkinnyShapeFactory::shapePath( shape, QSizeF( 50, 50 ) ); } + + class Pen : public QPen + { + public: + Pen( const QColor& color ) + : QPen( color ) + { + setCosmetic( true ); + setWidth( isCosmetic() ? 8 : 2 ); + + setJoinStyle( Qt::MiterJoin ); + + //setStyle( Qt::NoPen ); + //setStyle( Qt::DashLine ); + + //setAlpha( 100 ); + } + + void setAlpha( int alpha ) + { + setColor( QskRgb::toTransparent( color(), alpha ) ); + } + }; } int main( int argc, char* argv[] ) @@ -36,8 +60,9 @@ int main( int argc, char* argv[] ) auto shapeItem = new ShapeItem(); shapeItem->setPath( path( SkinnyShapeFactory::Hexagon ) ); - shapeItem->setPen( QPen( Qt::darkCyan, 20 ) ); - shapeItem->setGradient( Qt::red, Qt::blue ); + + shapeItem->setPen( Pen( QColorConstants::Svg::indigo ) ); + shapeItem->setGradient( QGradient::PhoenixStart ); window.addItem( shapeItem ); window.resize( 600, 600 ); diff --git a/src/nodes/QskShapeNode.cpp b/src/nodes/QskShapeNode.cpp index 0ccc5691..813f8234 100644 --- a/src/nodes/QskShapeNode.cpp +++ b/src/nodes/QskShapeNode.cpp @@ -15,9 +15,10 @@ QSK_QT_PRIVATE_BEGIN #include QSK_QT_PRIVATE_END -static void qskUpdateGeometry( const QPainterPath& path, QSGGeometry& geometry ) +static void qskUpdateGeometry( const QPainterPath& path, + const QTransform& transform, QSGGeometry& geometry ) { - const auto ts = qTriangulate( path, QTransform(), 1, false ); + const auto ts = qTriangulate( path, transform, 1, false ); #if 1 geometry.allocate( ts.vertices.size(), ts.indices.size() ); @@ -118,7 +119,8 @@ QskShapeNode::QskShapeNode() setFlag( QSGNode::OwnsMaterial, true ); } -void QskShapeNode::updateNode( const QPainterPath& path, const QColor& color ) +void QskShapeNode::updateNode( const QPainterPath& path, + const QTransform& transform, const QColor& color ) { Q_D( QskShapeNode ); @@ -130,7 +132,7 @@ void QskShapeNode::updateNode( const QPainterPath& path, const QColor& color ) if ( true ) // For the moment we always update the geometry. TODO ... { - qskUpdateGeometry( path, d->geometry ); + qskUpdateGeometry( path, transform, d->geometry ); markDirty( QSGNode::DirtyGeometry ); } @@ -150,7 +152,8 @@ void QskShapeNode::updateNode( const QPainterPath& path, const QColor& color ) } } -void QskShapeNode::updateNode( const QPainterPath& path, const QGradient* gradient ) +void QskShapeNode::updateNode( const QPainterPath& path, + const QTransform& transform, const QGradient* gradient ) { if ( path.isEmpty() || !qskIsGradientVisible( gradient ) ) { @@ -160,7 +163,7 @@ void QskShapeNode::updateNode( const QPainterPath& path, const QGradient* gradie if ( qskIsGradientMonochrome( gradient ) ) { - updateNode( path, gradient->stops().first().first ); + updateNode( path, transform, gradient->stops().first().first ); return; } @@ -168,7 +171,7 @@ void QskShapeNode::updateNode( const QPainterPath& path, const QGradient* gradie if ( true ) // For the moment we always update the geometry. TODO ... { - qskUpdateGeometry( path, d->geometry ); + qskUpdateGeometry( path, transform, d->geometry ); markDirty( QSGNode::DirtyGeometry ); } @@ -182,3 +185,4 @@ void QskShapeNode::updateNode( const QPainterPath& path, const QGradient* gradie if ( gradientMaterial->updateGradient( gradient ) ) markDirty( QSGNode::DirtyMaterial ); } + diff --git a/src/nodes/QskShapeNode.h b/src/nodes/QskShapeNode.h index 37c753b6..793c3d77 100644 --- a/src/nodes/QskShapeNode.h +++ b/src/nodes/QskShapeNode.h @@ -20,8 +20,8 @@ class QSK_EXPORT QskShapeNode : public QSGGeometryNode public: QskShapeNode(); - void updateNode( const QPainterPath&, const QGradient* ); - void updateNode( const QPainterPath&, const QColor& ); + void updateNode( const QPainterPath&, const QTransform&, const QGradient* ); + void updateNode( const QPainterPath&, const QTransform&, const QColor& ); private: Q_DECLARE_PRIVATE( QskShapeNode )