QskLinesNode introduced

This commit is contained in:
Uwe Rathmann 2023-05-17 14:21:40 +02:00
parent 81a90986b3
commit bf19d6464c
7 changed files with 566 additions and 0 deletions

View File

@ -32,6 +32,7 @@ list(APPEND HEADERS
common/QskShadowMetrics.h
common/QskSizePolicy.h
common/QskStateCombination.h
common/QskStippleMetrics.h
common/QskTextColors.h
common/QskTextOptions.h
)
@ -61,6 +62,7 @@ list(APPEND SOURCES
common/QskScaleTickmarks.cpp
common/QskShadowMetrics.cpp
common/QskSizePolicy.cpp
common/QskStippleMetrics.cpp
common/QskTextColors.cpp
common/QskTextOptions.cpp
)
@ -107,6 +109,7 @@ list(APPEND HEADERS
nodes/QskBoxShadowNode.h
nodes/QskColorRamp.h
nodes/QskGraphicNode.h
nodes/QskLinesNode.h
nodes/QskPaintedNode.h
nodes/QskPlainTextRenderer.h
nodes/QskRichTextRenderer.h
@ -135,6 +138,7 @@ list(APPEND SOURCES
nodes/QskBoxShadowNode.cpp
nodes/QskColorRamp.cpp
nodes/QskGraphicNode.cpp
nodes/QskLinesNode.cpp
nodes/QskPaintedNode.cpp
nodes/QskPlainTextRenderer.cpp
nodes/QskRectangleNode.cpp

View File

@ -0,0 +1,83 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskStippleMetrics.h"
#include <qhashfunctions.h>
#include <qpen.h>
#include <qvariant.h>
static void qskRegisterStippleMetrics()
{
qRegisterMetaType< QskStippleMetrics >();
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QMetaType::registerEqualsComparator< QskStippleMetrics >();
#endif
}
static inline QVector< qreal > qskDashPattern( const Qt::PenStyle& style )
{
static QVector< qreal > pattern[] =
{
{}, { 1 }, { 4, 2 }, { 1, 2 },
{ 4, 2, 1, 2 }, { 4, 2, 1, 2, 1, 2 }, {}
};
return pattern[ style ];
}
Q_CONSTRUCTOR_FUNCTION( qskRegisterStippleMetrics )
QskStippleMetrics::QskStippleMetrics( Qt::PenStyle penStyle )
: m_pattern( qskDashPattern( penStyle ) )
{
}
QskStippleMetrics::QskStippleMetrics( const QPen& pen )
: QskStippleMetrics( pen.style() )
{
if ( pen.style() == Qt::CustomDashLine )
{
m_offset = pen.dashOffset();
m_pattern = pen.dashPattern();
}
}
void QskStippleMetrics::setPattern( const QVector< qreal >& pattern )
{
m_pattern = pattern;
}
void QskStippleMetrics::setOffset( qreal offset ) noexcept
{
m_offset = offset;
}
QskHashValue QskStippleMetrics::hash( QskHashValue seed ) const noexcept
{
auto hash = qHash( m_offset, seed );
return qHash( m_pattern, hash );
}
#ifndef QT_NO_DEBUG_STREAM
#include <qdebug.h>
QDebug operator<<( QDebug debug, const QskStippleMetrics& metrics )
{
QDebugStateSaver saver( debug );
debug.nospace();
debug << "QskStippleMetrics" << '(';
debug << metrics.offset() << ',' << metrics.pattern();
debug << ')';
return debug;
}
#endif
#include "moc_QskStippleMetrics.cpp"

View File

@ -0,0 +1,97 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_STIPPLE_METRICS_H
#define QSK_STIPPLE_METRICS_H
#include "QskGlobal.h"
#include <qmetatype.h>
#include <qvector.h>
#include <qnamespace.h>
class QPen;
class QSK_EXPORT QskStippleMetrics
{
Q_GADGET
Q_PROPERTY( qreal offset READ offset WRITE setOffset )
Q_PROPERTY( QVector< qreal > pattern READ pattern WRITE setPattern )
public:
QskStippleMetrics( Qt::PenStyle = Qt::SolidLine );
QskStippleMetrics( const QPen& );
QskStippleMetrics( const QVector< qreal >&, qreal offset = 0.0 );
bool operator==( const QskStippleMetrics& ) const noexcept;
bool operator!=( const QskStippleMetrics& ) const noexcept;
bool isValid() const noexcept;
bool isSolid() const noexcept;
void setOffset( qreal offset ) noexcept;
qreal offset() const noexcept;
void setPattern( const QVector< qreal >& );
QVector< qreal > pattern() const;
QskHashValue hash( QskHashValue seed = 0 ) const noexcept;
private:
qreal m_offset = 0.0;
QVector< qreal > m_pattern;
};
inline QskStippleMetrics::QskStippleMetrics(
const QVector< qreal >& pattern, qreal offset )
: m_offset( offset )
, m_pattern( pattern )
{
}
inline qreal QskStippleMetrics::offset() const noexcept
{
return m_offset;
}
inline QVector< qreal > QskStippleMetrics::pattern() const
{
return m_pattern;
}
inline bool QskStippleMetrics::operator==(
const QskStippleMetrics& other ) const noexcept
{
return ( m_offset == other.m_offset )
&& ( m_pattern == other.m_pattern );
}
inline bool QskStippleMetrics::operator!=(
const QskStippleMetrics& other ) const noexcept
{
return !( *this == other );
}
inline bool QskStippleMetrics::isValid() const noexcept
{
return !m_pattern.isEmpty();
}
inline bool QskStippleMetrics::isSolid() const noexcept
{
return m_pattern.count() == 1;
}
#ifndef QT_NO_DEBUG_STREAM
class QDebug;
QSK_EXPORT QDebug operator<<( QDebug, const QskStippleMetrics& );
#endif
Q_DECLARE_METATYPE( QskStippleMetrics )
#endif

327
src/nodes/QskLinesNode.cpp Normal file
View File

@ -0,0 +1,327 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskLinesNode.h"
#include "QskIntervalF.h"
#include "QskVertex.h"
#include "QskStippleMetrics.h"
#include "QskSGNode.h"
#include <qsgflatcolormaterial.h>
#include <qsgvertexcolormaterial.h>
#include <qtransform.h>
#include <qquickitem.h>
#include <qquickwindow.h>
QSK_QT_PRIVATE_BEGIN
#include <private/qsgnode_p.h>
#include <private/qstroker_p.h>
QSK_QT_PRIVATE_END
namespace
{
inline qreal mapX( const QTransform& t, qreal x )
{
return t.dx() + t.m11() * x;
}
inline qreal mapY( const QTransform& t, qreal y )
{
return t.dy() + t.m22() * y;
}
/*
Thanks to the hooks of the stroker classes we can make use
of QDashStroker without having to deal with the overhead of
QPainterPaths. But it might be worth to check if this could
be done in a shader. TODO ...
*/
class DashStroker : public QDashStroker
{
public:
DashStroker( const QskStippleMetrics& metrics )
: QDashStroker( nullptr )
{
setDashOffset( metrics.offset() );
setDashPattern( metrics.pattern() );
m_elements.reserve( 2 );
}
QSGGeometry::Point2D* addDashes( QSGGeometry::Point2D* points,
qreal x1, qreal y1, qreal x2, qreal y2 )
{
setMoveToHook( addPoint );
setLineToHook( addPoint );
m_points = points;
begin( this );
m_elements.add( { QPainterPath::MoveToElement, x1, y1 } );
m_elements.add( { QPainterPath::LineToElement, x2, y2 } );
processCurrentSubpath();
end();
return m_points;
}
int pointCount( qreal x1, qreal y1, qreal x2, qreal y2 )
{
/*
There should be a faster way to calculate the
number of points. TODO ...
*/
setMoveToHook( countPoint );
setLineToHook( countPoint );
m_count = 0;
begin( this );
m_elements.add( { QPainterPath::MoveToElement, x1, y1 } );
m_elements.add( { QPainterPath::LineToElement, x2, y2 } );
processCurrentSubpath();
end();
return m_count;
}
private:
static void addPoint( qfixed x, qfixed y, void* data )
{
auto stroker = reinterpret_cast< DashStroker* >( data );
( stroker->m_points++ )->set( x, y );
}
static void countPoint( qfixed, qfixed, void* data )
{
auto stroker = reinterpret_cast< DashStroker* >( data );
stroker->m_count++;
}
int m_count = 0;
QSGGeometry::Point2D* m_points;
};
}
class QskLinesNodePrivate final : public QSGGeometryNodePrivate
{
public:
QskLinesNodePrivate()
: geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
{
geometry.setDrawingMode( QSGGeometry::DrawLines );
geometry.setVertexDataPattern( QSGGeometry::StaticPattern );
}
inline qreal round( bool isHorizontal, qreal v ) const
{
const auto r2 = 2.0 * devicePixelRatio;
const qreal v0 = isHorizontal ? p0.x() : p0.y();
const int d = qRound( r2 * ( v + v0 ) );
const auto f = ( d % 2 ? d : d - 1 ) / r2;
return f / devicePixelRatio - v0;
}
QSGGeometry geometry;
QSGFlatColorMaterial material;
// position of [0,0] in device coordinates
QPointF p0;
qreal devicePixelRatio = 1.0;
QskHashValue hash = 0.0;
bool dirty = true;
};
QskLinesNode::QskLinesNode()
: QSGGeometryNode( *new QskLinesNodePrivate )
{
Q_D( QskLinesNode );
setGeometry( &d->geometry );
setMaterial( &d->material );
}
QskLinesNode::~QskLinesNode()
{
}
void QskLinesNode::setGlobalPosition( const QQuickItem* item )
{
QPointF p0;
qreal devicePixelRatio = 1.0;
if ( item )
{
p0 = item->mapToGlobal( QPointF() );
if ( auto w = item->window() )
devicePixelRatio = w->devicePixelRatio();
}
setGlobalPosition( p0, devicePixelRatio );
}
void QskLinesNode::setGlobalPosition(
const QPointF& pos, qreal devicePixelRatio )
{
Q_D( QskLinesNode );
if ( pos != d->p0 || devicePixelRatio != d->devicePixelRatio )
{
d->p0 = pos;
d->devicePixelRatio = devicePixelRatio;
d->dirty = true;
}
}
void QskLinesNode::updateGrid( const QColor& color, qreal lineWidth,
const QskStippleMetrics& stippleMetrics, const QTransform& transform,
const QskIntervalF& xBoundaries, const QVector< qreal >& xValues,
const QskIntervalF& yBoundaries, const QVector< qreal >& yValues )
{
Q_D( QskLinesNode );
if ( color != d->material.color() )
{
d->material.setColor( color );
markDirty( QSGNode::DirtyMaterial );
}
QskHashValue hash = 9784;
hash = stippleMetrics.hash( hash );
hash = qHash( transform, hash );
hash = qHashBits( &xBoundaries, sizeof( xBoundaries ), hash );
hash = qHashBits( &yBoundaries, sizeof( yBoundaries ), hash );
hash = qHash( xValues, hash );
hash = qHash( yValues, hash );
if ( hash != d->hash )
{
d->dirty = true;
d->hash = hash;
}
if( d->dirty )
{
if ( !( stippleMetrics.isValid()
&& color.isValid() && color.alpha() > 0 ) )
{
QskSGNode::resetGeometry( this );
}
else
{
updateGeometry( stippleMetrics, transform,
xBoundaries, xValues, yBoundaries, yValues );
}
markDirty( QSGNode::DirtyGeometry );
d->dirty = false;
}
const float lineWidthF = lineWidth;
if( lineWidthF != d->geometry.lineWidth() )
d->geometry.setLineWidth( lineWidthF );
}
void QskLinesNode::updateGeometry(
const QskStippleMetrics& stippleMetrics, const QTransform& transform,
const QskIntervalF& xBoundaries, const QVector< qreal >& xValues,
const QskIntervalF& yBoundaries, const QVector< qreal >& yValues )
{
Q_D( QskLinesNode );
const auto x1 = mapX( transform, xBoundaries.lowerBound() );
const auto x2 = mapX( transform, xBoundaries.upperBound() );
const auto y1 = mapY( transform, yBoundaries.lowerBound() );
const auto y2 = mapY( transform, yBoundaries.upperBound() );
if ( stippleMetrics.isSolid() )
{
using namespace QskVertex;
auto lines = allocateLines< Line >(
d->geometry, xValues.count() + yValues.count() );
for ( auto x : xValues )
{
x = mapX( transform, x );
x = d->round( true, x );
lines++->setVLine( x, y1, y2 );
}
for ( auto y : yValues )
{
y = mapY( transform, y );
y = d->round( false, y );
lines++->setHLine( x1, x2, y );
}
}
else
{
DashStroker stroker( stippleMetrics );
const int countX = stroker.pointCount( 0.0, y1, 0.0, y2 );
const int countY = stroker.pointCount( x1, 0.0, x2, 0.0 );
auto count = xValues.count() * countX + yValues.count() * countY;
d->geometry.allocate( count );
auto points = d->geometry.vertexDataAsPoint2D();
/*
We have to calculate the first line only. All others are
translation without changing the positions of the dashes.
*/
auto p0 = points;
for ( int i = 0; i < xValues.count(); i++ )
{
auto x = mapX( transform, xValues[i] );
x = d->round( true, x );
if ( i == 0 )
{
points = stroker.addDashes( points, x, y1, x, y2 );
}
else
{
for ( int j = 0; j < countX; j++ )
points++->set( x, p0[j].y );
}
}
p0 = points;
for ( int i = 0; i < yValues.count(); i++ )
{
auto y = mapY( transform, yValues[i] );
y = d->round( false, y );
if ( i == 0 )
{
points = stroker.addDashes( points, x1, y, x2, y );
}
else
{
for ( int j = 0; j < countY; j++ )
points++->set( p0[j].x, y );
}
}
}
}

52
src/nodes/QskLinesNode.h Normal file
View File

@ -0,0 +1,52 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_LINES_NODE_H
#define QSK_LINES_NODE_H
#include "QskGlobal.h"
#include <qsgnode.h>
#include <qvector.h>
class QskIntervalF;
class QskStippleMetrics;
class QTransform;
class QPointF;
class QQuickItem;
class QskLinesNodePrivate;
/*
A node for stippled or solid lines.
For the moment limited to horizontal/vertical lines: TODO
*/
class QSK_EXPORT QskLinesNode : public QSGGeometryNode
{
public:
QskLinesNode();
~QskLinesNode() override;
void setGlobalPosition( const QPointF&, qreal devicePixelRatio );
void setGlobalPosition( const QQuickItem* );
void setLineColor( const QColor& );
void setLineWidth( qreal );
void updateGrid( const QColor&, qreal lineWidth,
const QskStippleMetrics&, const QTransform&,
const QskIntervalF&, const QVector< qreal >&,
const QskIntervalF&, const QVector< qreal >& );
private:
void updateGeometry(
const QskStippleMetrics&, const QTransform&,
const QskIntervalF&, const QVector< qreal >&,
const QskIntervalF&, const QVector< qreal >& );
Q_DECLARE_PRIVATE( QskLinesNode )
};
#endif

View File

@ -86,6 +86,8 @@ QskStrokeNode::QskStrokeNode()
setMaterial( qskMaterialColorVertex );
}
QskStrokeNode::~QskStrokeNode() = default;
void QskStrokeNode::setRenderHint( RenderHint renderHint )
{
Q_D( QskStrokeNode );

View File

@ -19,6 +19,7 @@ class QSK_EXPORT QskStrokeNode : public QSGGeometryNode
{
public:
QskStrokeNode();
~QskStrokeNode() override;
/*
We only support monochrome pens ( QPen::color() ) and using a