working towards an API, that works for QML

This commit is contained in:
Uwe Rathmann 2022-12-02 16:30:01 +01:00
parent 103746d719
commit 2fce815925
6 changed files with 362 additions and 63 deletions

View File

@ -4,11 +4,16 @@
*****************************************************************************/
#include "ShapeItem.h"
#include "Stroke.h"
#include <QskGradient.h>
#include <QskStrokeNode.h>
#include <QskShapeNode.h>
#include <QskSGNode.h>
#include <qpainterpath.h>
#include <qpen.h>
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
{

View File

@ -5,35 +5,51 @@
#pragma once
#include <QskGradient.h>
#include <QskControl.h>
#include <QPen>
#include <QPainterPath>
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;
};

View File

@ -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 <QPen>
#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"

154
playground/shapes/Stroke.h Normal file
View File

@ -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 <QMetaType>
#include <QColor>
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 );
}

View File

@ -4,6 +4,7 @@
*****************************************************************************/
#include "ShapeItem.h"
#include "Stroke.h"
#include <QskObjectCounter.h>
#include <QskWindow.h>
@ -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 ) );

View File

@ -1,8 +1,10 @@
CONFIG += qskexample
HEADERS += \
Stroke.h \
ShapeItem.h \
SOURCES += \
Stroke.cpp \
ShapeItem.cpp \
main.cpp