/****************************************************************************** * QSkinny - Copyright (C) The authors * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ #include "ShapeItem.h" #include "Stroke.h" #include #include #include #include #include #include #define STROKE_ADJUSTMENT 0 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() ); } #if STROKE_ADJUSTMENT static inline qreal effectiveStrokeWidth( const Stroke& stroke, const QRectF& r1, const QRectF& r2 ) { qreal width = qMax( stroke.width(), 0.0 ); if ( !stroke.isCosmetic() ) { const qreal sx = r1.width() / r2.width(); const qreal sy = r1.height() / r2.height(); width *= std::min( sx, sy ); } return width; } #endif class ShapeItem::PrivateData { public: inline PrivateData() : stroke( Qt::black, -1.0 ) // no stroke , gradient( Qt::white ) { } Stroke stroke; QskGradient gradient; QPainterPath path; }; ShapeItem::ShapeItem( QQuickItem* parent ) : QskControl( parent ) , m_data( new PrivateData() ) { setMargins( 20 ); setSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Ignored ); } ShapeItem::~ShapeItem() { } void ShapeItem::setStroke( const QColor& color, qreal width ) { setStroke( Stroke( color, width ) ); } void ShapeItem::setStroke( const Stroke& stroke ) { if ( stroke != m_data->stroke ) { m_data->stroke = stroke; update(); Q_EMIT strokeChanged( m_data->stroke ); } } void ShapeItem::resetStroke() { setStroke( Stroke( Qt::black, -1.0 ) ); } Stroke ShapeItem::stroke() const { return m_data->stroke; } void ShapeItem::setGradient( const QskGradient& gradient ) { if ( gradient != m_data->gradient ) { m_data->gradient = gradient; update(); Q_EMIT gradientChanged( m_data->gradient ); } } void ShapeItem::resetGradient() { setGradient( Qt::white ); } QskGradient ShapeItem::gradient() const { return m_data->gradient; } void ShapeItem::setPath( const QPainterPath& path ) { if ( path != m_data->path ) { m_data->path = path; update(); Q_EMIT pathChanged( m_data->path ); } } void ShapeItem::resetPath() { setPath( QPainterPath() ); } QPainterPath ShapeItem::path() const { return m_data->path; } void ShapeItem::updateNode( QSGNode* parentNode ) { enum NodeRole { FillRole, BorderRole }; const auto rect = contentsRect(); const auto pathRect = m_data->path.controlPointRect(); auto fillNode = static_cast< QskShapeNode* >( QskSGNode::findChildNode( parentNode, FillRole ) ); auto borderNode = static_cast< QskStrokeNode* >( QskSGNode::findChildNode( parentNode, BorderRole ) ); if ( rect.isEmpty() || pathRect.isEmpty() ) { delete fillNode; delete borderNode; return; } if ( m_data->gradient.isVisible() ) { if ( fillNode == nullptr ) { fillNode = new QskShapeNode; QskSGNode::setNodeRole( fillNode, FillRole ); parentNode->prependChildNode( fillNode ); } auto fillRect = rect; #if STROKE_ADJUSTMENT /* when the stroke is not opaque ( transparent color or dashed ) we would see, that the filling is not inside. But simply adjusting by the half of the stroke width is only correct for rectangles. TODO ... */ const auto pw2 = 0.5 * ::effectiveStrokeWidth( m_data->stroke, rect, pathRect ); fillRect.adjust( pw2, pw2, -pw2, -pw2 ); #endif const auto transform = ::transformForRects( pathRect, fillRect ); fillNode->updateNode( m_data->path, transform, fillRect, m_data->gradient ); } else { delete fillNode; } if ( m_data->stroke.isVisible() ) { const auto pen = m_data->stroke.toPen(); #if 0 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 ... */ } } #endif if ( borderNode == nullptr ) { borderNode = new QskStrokeNode; QskSGNode::setNodeRole( borderNode, BorderRole ); parentNode->appendChildNode( borderNode ); } const auto transform = ::transformForRects( pathRect, rect ); borderNode->updatePath( m_data->path, transform, pen ); } else { delete borderNode; } } #include "moc_ShapeItem.cpp"