From 2fce815925db06d0167d5305a9dd068ab78386dc Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Fri, 2 Dec 2022 16:30:01 +0100 Subject: [PATCH] working towards an API, that works for QML --- playground/shapes/ShapeItem.cpp | 117 ++++++++++++++++-------- playground/shapes/ShapeItem.h | 40 ++++++--- playground/shapes/Stroke.cpp | 78 ++++++++++++++++ playground/shapes/Stroke.h | 154 ++++++++++++++++++++++++++++++++ playground/shapes/main.cpp | 34 ++++--- playground/shapes/shapes.pro | 2 + 6 files changed, 362 insertions(+), 63 deletions(-) create mode 100644 playground/shapes/Stroke.cpp create mode 100644 playground/shapes/Stroke.h diff --git a/playground/shapes/ShapeItem.cpp b/playground/shapes/ShapeItem.cpp index 821f6bc0..21a1a02d 100644 --- a/playground/shapes/ShapeItem.cpp +++ b/playground/shapes/ShapeItem.cpp @@ -4,11 +4,16 @@ *****************************************************************************/ #include "ShapeItem.h" +#include "Stroke.h" +#include #include #include #include +#include +#include + static inline QTransform transformForRects( const QRectF& r1, const QRectF& r2 ) { return QTransform::fromTranslate( -r1.x(), -r1.y() ) @@ -16,22 +21,12 @@ static inline QTransform transformForRects( const QRectF& r1, const QRectF& r2 ) * QTransform::fromTranslate( r2.x(), r2.y() ); } -static inline bool isVisible( const QColor& color ) +static inline qreal effectiveStrokeWidth( + const Stroke& stroke, const QRectF& r1, const QRectF& r2 ) { - return color.isValid() && ( color.alpha() > 0 ); -} + qreal width = qMax( stroke.width(), 0.0 ); -static inline bool isVisible( const QPen& pen ) -{ - return ( pen.style() != Qt::NoPen ) && isVisible( pen.color() ); -} - -static inline qreal effectivePenWidth( - const QPen& pen, const QRectF& r1, const QRectF& r2 ) -{ - qreal width = pen.widthF(); - - if ( !pen.isCosmetic() ) + if ( !stroke.isCosmetic() ) { const qreal sx = r1.width() / r2.width(); const qreal sy = r1.height() / r2.height(); @@ -42,8 +37,23 @@ static inline qreal effectivePenWidth( return width; } +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 ); @@ -53,46 +63,72 @@ ShapeItem::~ShapeItem() { } -void ShapeItem::setPen( const QPen& pen ) +void ShapeItem::setStroke( const QColor& color, qreal width ) { - if ( pen != m_pen ) + setStroke( Stroke( color, width ) ); +} + +void ShapeItem::setStroke( const Stroke& stroke ) +{ + if ( stroke != m_data->stroke ) { - m_pen = pen; + m_data->stroke = stroke; update(); + + Q_EMIT strokeChanged( m_data->stroke ); } } -QPen ShapeItem::pen() const +void ShapeItem::resetStroke() { - return m_pen; + setStroke( Stroke( Qt::black, -1.0 ) ); +} + +Stroke ShapeItem::stroke() const +{ + return m_data->stroke; } void ShapeItem::setGradient( const QskGradient& gradient ) { - if ( gradient != m_gradient ) + if ( gradient != m_data->gradient ) { - m_gradient = gradient; + m_data->gradient = gradient; update(); + + Q_EMIT gradientChanged( m_data->gradient ); } } -const QskGradient& ShapeItem::gradient() const +void ShapeItem::resetGradient() { - return m_gradient; + setGradient( Qt::white ); +} + +QskGradient ShapeItem::gradient() const +{ + return m_data->gradient; } void ShapeItem::setPath( const QPainterPath& path ) { - if ( path != m_path ) + if ( path != m_data->path ) { - m_path = path; + m_data->path = path; update(); + + Q_EMIT pathChanged( m_data->path ); } } +void ShapeItem::resetPath() +{ + setPath( QPainterPath() ); +} + QPainterPath ShapeItem::path() const { - return m_path; + return m_data->path; } void ShapeItem::updateNode( QSGNode* parentNode ) @@ -104,7 +140,7 @@ void ShapeItem::updateNode( QSGNode* parentNode ) }; const auto rect = contentsRect(); - const auto pathRect = m_path.controlPointRect(); + const auto pathRect = m_data->path.controlPointRect(); auto fillNode = static_cast< QskShapeNode* >( QskSGNode::findChildNode( parentNode, FillRole ) ); @@ -120,7 +156,7 @@ void ShapeItem::updateNode( QSGNode* parentNode ) return; } - if ( m_gradient.isVisible() ) + if ( m_data->gradient.isVisible() ) { if ( fillNode == nullptr ) { @@ -131,26 +167,31 @@ void ShapeItem::updateNode( QSGNode* parentNode ) } auto fillRect = rect; - if ( m_pen.style() != Qt::NoPen ) - { - const auto pw2 = 0.5 * ::effectivePenWidth( m_pen, rect, pathRect ); - fillRect.adjust( pw2, pw2, -pw2, -pw2 ); - } +#if 0 + /* + 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_path, transform, fillRect, m_gradient ); + fillNode->updateNode( m_data->path, transform, fillRect, m_data->gradient ); } else { delete fillNode; } - if ( ::isVisible( m_pen ) ) + if ( m_data->stroke.isVisible() ) { + const auto pen = m_data->stroke.toPen(); #if 0 - if ( m_pen.widthF() > 1.0 ) + if ( pen.widthF() > 1.0 ) { - if ( !( m_pen.isSolid() && m_pen.color().alpha() == 255 ) ) + if ( !( pen.isSolid() && pen.color().alpha() == 255 ) ) { /* We might end up with overlapping parts @@ -173,7 +214,7 @@ void ShapeItem::updateNode( QSGNode* parentNode ) } const auto transform = ::transformForRects( pathRect, rect ); - borderNode->updateNode( m_path, transform, m_pen ); + borderNode->updateNode( m_data->path, transform, pen ); } else { diff --git a/playground/shapes/ShapeItem.h b/playground/shapes/ShapeItem.h index 70841e17..704cce81 100644 --- a/playground/shapes/ShapeItem.h +++ b/playground/shapes/ShapeItem.h @@ -5,35 +5,51 @@ #pragma once -#include #include -#include -#include + +class Stroke; +class QskGradient; +class QPainterPath; class ShapeItem : public QskControl { Q_OBJECT + Q_PROPERTY( Stroke stroke READ stroke WRITE setStroke + RESET resetStroke NOTIFY strokeChanged ) + + Q_PROPERTY( QskGradient gradient READ gradient WRITE setGradient + RESET resetGradient NOTIFY gradientChanged ) + + Q_PROPERTY( QPainterPath path READ path WRITE setPath + RESET resetPath NOTIFY pathChanged ) + public: ShapeItem( QQuickItem* parent = nullptr ); ~ShapeItem() override; - void setPen( const QPen& ); - QPen pen() const; + Stroke stroke() const; + void setStroke( const Stroke& ); + void setStroke( const QColor&, qreal width = 1.0 ); + void resetStroke(); + QskGradient gradient() const; void setGradient( const QskGradient& ); - const QskGradient& gradient() const; + void resetGradient(); - void setPath( const QPainterPath& ); QPainterPath path() const; + void setPath( const QPainterPath& ); + void resetPath(); + + Q_SIGNALS: + void strokeChanged( const Stroke& ); + void gradientChanged( const QskGradient& ); + void pathChanged( const QPainterPath& ); protected: void updateNode( QSGNode* ) override; private: - QPainterPath scaledPath( const QRectF& ) const; - - QPen m_pen; - QskGradient m_gradient; - QPainterPath m_path; + class PrivateData; + std::unique_ptr< PrivateData > m_data; }; diff --git a/playground/shapes/Stroke.cpp b/playground/shapes/Stroke.cpp new file mode 100644 index 00000000..d33a5143 --- /dev/null +++ b/playground/shapes/Stroke.cpp @@ -0,0 +1,78 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#include "Stroke.h" +#include + +#if 0 +Stroke::Stroke( const QPen& pen ) noexcept + : m_width( pen.widthF() ) + , m_miterLimit( pen.miterLimit() ) + , m_color( pen.color() ) + , m_lineStyle( ( pen.style() == Qt::DashLine ) ? DashLine : SolidLine ) + , m_joinStyle( static_cast< JoinStyle >( pen.joinStyle() ) ) + , m_capStyle( static_cast< CapStyle >( pen.capStyle() ) ) + , m_cosmetic( pen.isCosmetic() ) +{ +} + +#endif + +void Stroke::setColor( const QColor& color ) noexcept +{ + m_color = color; +} + +void Stroke::setWidth( qreal width ) noexcept +{ + m_width = width; +} + +void Stroke::setLineStyle( LineStyle style ) +{ + m_lineStyle = style; +} + +void Stroke::setCapStyle( CapStyle capStyle ) noexcept +{ + m_capStyle = capStyle; +} + +void Stroke::setJoinStyle( JoinStyle joinStyle ) noexcept +{ + m_joinStyle = joinStyle; +} + +void Stroke::setMiterLimit( int miterLimit ) noexcept +{ + m_miterLimit = miterLimit; +} + +void Stroke::setCosmetic( bool on ) noexcept +{ + m_cosmetic = on; +} + +bool Stroke::isVisible() const +{ + return ( m_width > 0.0 ) && m_color.isValid() && ( m_color.alpha() > 0 ); +} + +QPen Stroke::toPen() const +{ + QPen pen( + m_color, + m_width, + ( m_width > 0.0 ) ? static_cast< Qt::PenStyle >( m_lineStyle ) : Qt::NoPen, + static_cast< Qt::PenCapStyle >( m_capStyle ), + static_cast< Qt::PenJoinStyle >( m_joinStyle ) + ); + + pen.setCosmetic( m_cosmetic ); + + return pen; +} + +#include "moc_Stroke.cpp" diff --git a/playground/shapes/Stroke.h b/playground/shapes/Stroke.h new file mode 100644 index 00000000..025f3fbb --- /dev/null +++ b/playground/shapes/Stroke.h @@ -0,0 +1,154 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the 3-clause BSD License + *****************************************************************************/ + +#pragma once + +#include +#include + +class QPen; + +class Stroke +{ + Q_GADGET + + Q_PROPERTY( QColor color READ color WRITE setColor ) + + Q_PROPERTY( LineStyle lineStyle READ lineStyle WRITE setLineStyle ) + Q_PROPERTY( qreal width READ width WRITE setWidth ) + Q_PROPERTY( JoinStyle joinStyle READ joinStyle WRITE setJoinStyle ) + Q_PROPERTY( int miterLimit READ miterLimit WRITE setMiterLimit ) + Q_PROPERTY( CapStyle capStyle READ capStyle WRITE setCapStyle ) + + Q_PROPERTY( bool cosmetic READ isCosmetic WRITE setCosmetic ) + + public: + enum LineStyle + { + SolidLine = Qt::SolidLine, + DashLine = Qt::DashLine + }; + Q_ENUM( LineStyle ) + + enum JoinStyle + { + MiterJoin = Qt::MiterJoin, + BevelJoin = Qt::BevelJoin, + RoundJoin = Qt::RoundJoin + }; + Q_ENUM( JoinStyle ) + + enum CapStyle + { + FlatCap = Qt::FlatCap, + SquareCap = Qt::SquareCap, + RoundCap = Qt::RoundCap + }; + Q_ENUM( CapStyle ) + + Stroke() noexcept = default; + Stroke( const QColor&, qreal width = 1.0 ) noexcept; +#if 0 + Stroke( const QPen& ) noexcept; +#endif + + bool operator==( const Stroke& ) const noexcept; + bool operator!=( const Stroke& ) const noexcept; + + void setColor( const QColor& ) noexcept; + QColor color() const noexcept; + + void setWidth( qreal ) noexcept; + qreal width() const noexcept; + + void setLineStyle( LineStyle ); + LineStyle lineStyle() const noexcept; + + void setCapStyle( CapStyle ) noexcept; + CapStyle capStyle() const noexcept; + + void setJoinStyle( JoinStyle ) noexcept; + JoinStyle joinStyle() const noexcept; + + void setMiterLimit( int ) noexcept; + int miterLimit() const noexcept; + + void setCosmetic( bool ) noexcept; + bool isCosmetic() const noexcept; + + bool isVisible() const; + + QPen toPen() const; + + private: + qreal m_width = 1.0; + + int m_miterLimit = 2; + + QColor m_color = Qt::black; + + LineStyle m_lineStyle = SolidLine; + JoinStyle m_joinStyle = BevelJoin; + CapStyle m_capStyle = SquareCap; + + bool m_cosmetic = false; +}; + +inline Stroke::Stroke( const QColor& color, qreal width ) noexcept + : m_width( width ) + , m_color( color ) +{ +} + +inline QColor Stroke::color() const noexcept +{ + return m_color; +} + +inline qreal Stroke::width() const noexcept +{ + return m_width; +} + +inline Stroke::LineStyle Stroke::lineStyle() const noexcept +{ + return m_lineStyle; +} + +inline Stroke::CapStyle Stroke::capStyle() const noexcept +{ + return m_capStyle; +} + +inline Stroke::JoinStyle Stroke::joinStyle() const noexcept +{ + return m_joinStyle; +} + +inline int Stroke::miterLimit() const noexcept +{ + return m_miterLimit; +} + +inline bool Stroke::isCosmetic() const noexcept +{ + return m_cosmetic; +} + +inline bool Stroke::operator==( const Stroke& other ) const noexcept +{ + return ( m_width == other.m_width ) + && ( m_miterLimit == other.m_miterLimit ) + && ( m_color == other.m_color ) + && ( m_lineStyle == other.m_lineStyle ) + && ( m_joinStyle == other.m_joinStyle ) + && ( m_capStyle == other.m_capStyle ) + && ( m_cosmetic == other.m_cosmetic ); +} + +inline bool Stroke::operator!=( const Stroke& other ) const noexcept +{ + return !( *this == other ); +} diff --git a/playground/shapes/main.cpp b/playground/shapes/main.cpp index 50510fa1..ab41aaf8 100644 --- a/playground/shapes/main.cpp +++ b/playground/shapes/main.cpp @@ -4,6 +4,7 @@ *****************************************************************************/ #include "ShapeItem.h" +#include "Stroke.h" #include #include @@ -28,21 +29,21 @@ namespace } protected: - static QPen pen( const QColor& color ) + static Stroke createStroke( const QColor& color ) { - QPen p( color ); + Stroke stroke( color ); #if 0 - p.setCosmetic( true ); + stroke.setCosmetic( true ); #endif - p.setWidth( p.isCosmetic() ? 8 : 2 ); - p.setJoinStyle( Qt::MiterJoin ); + stroke.setWidth( stroke.isCosmetic() ? 8 : 2 ); + stroke.setJoinStyle( Stroke::MiterJoin ); #if 0 - p.setStyle( Qt::DashLine ); - p.setColor( QskRgb::toTransparent( color(), alpha ) ); + stroke.setLineStyle( Stroke::DashLine ); + stroke.setColor( QskRgb::toTransparent( color(), alpha ) ); #endif - return p; + return stroke; } static QPainterPath path( SkinnyShapeFactory::Shape shape ) @@ -61,7 +62,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::Hexagon ) ); - shapeItem->setPen( pen( QColorConstants::Svg::indigo ) ); + shapeItem->setStroke( createStroke( QColorConstants::Svg::indigo ) ); QskGradient gradient( QGradient::PhoenixStart ); gradient.setLinearDirection( 0.0, 0.0, 0.2, 0.5 ); @@ -72,7 +73,9 @@ namespace { auto shapeItem = new ShapeItem( this ); + shapeItem->setPath( path( SkinnyShapeFactory::Star ) ); + shapeItem->setStroke( Qt::black ); const QVector< QskGradientStop > stops = { { 0.5, QskRgb::RoyalBlue }, { 0.5, QskRgb::LemonChiffon } }; @@ -85,7 +88,9 @@ namespace } { auto shapeItem = new ShapeItem( this ); + shapeItem->setPath( path( SkinnyShapeFactory::Rectangle ) ); + shapeItem->setStroke( Qt::black ); const QVector< QskGradientStop > stops = { { 0.5, QskRgb::MediumVioletRed }, { 0.5, QskRgb::Navy } }; @@ -108,7 +113,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::Rectangle ) ); - shapeItem->setPen( pen( QColorConstants::Svg::indigo ) ); + shapeItem->setStroke( createStroke( QColorConstants::Svg::indigo ) ); QskGradient gradient( QskRgb::LightYellow, QskRgb::MidnightBlue ); gradient.setRadialDirection( QskRadialDirection() ); @@ -120,7 +125,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::Ellipse ) ); - + shapeItem->setStroke( Qt::black ); QVector< QskGradientStop > stops; @@ -145,7 +150,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::Rectangle ) ); - shapeItem->setPen( pen( QskRgb::Indigo ) ); + shapeItem->setStroke( createStroke( QskRgb::Indigo ) ); QskGradient gradient( QGradient::LilyMeadow ); gradient.setRadialDirection( 0.5, 0.7, 0.25 ); @@ -157,7 +162,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::Rectangle ) ); - shapeItem->setPen( pen( QskRgb::Indigo ) ); + shapeItem->setStroke( createStroke( QskRgb::Indigo ) ); QskGradient gradient( Qt::red, Qt::blue ); gradient.setRadialDirection( 0.6, 0.4, 0.1 ); @@ -178,6 +183,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::Ellipse ) ); + shapeItem->setStroke( Qt::black ); QskGradient gradient( QGradient::JuicyPeach ); gradient.setConicDirection( 0.5, 0.5, 30.0, 60.0 ); @@ -189,6 +195,7 @@ namespace auto shapeItem = new ShapeItem( this ); shapeItem->setPath( path( SkinnyShapeFactory::TriangleUp ) ); + shapeItem->setStroke( Qt::black ); QskGradient gradient( QGradient::WinterNeva ); gradient.setConicDirection( 0.5, 0.5, 30.0, 60.0 ); @@ -198,6 +205,7 @@ namespace } { auto shapeItem = new ShapeItem( this ); + shapeItem->setStroke( Qt::black ); shapeItem->setPath( path( SkinnyShapeFactory::Arc ) ); diff --git a/playground/shapes/shapes.pro b/playground/shapes/shapes.pro index bbf1741a..9e37f161 100644 --- a/playground/shapes/shapes.pro +++ b/playground/shapes/shapes.pro @@ -1,8 +1,10 @@ CONFIG += qskexample HEADERS += \ + Stroke.h \ ShapeItem.h \ SOURCES += \ + Stroke.cpp \ ShapeItem.cpp \ main.cpp