QskLinesNode introduced
This commit is contained in:
parent
81a90986b3
commit
bf19d6464c
@ -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
|
||||
|
83
src/common/QskStippleMetrics.cpp
Normal file
83
src/common/QskStippleMetrics.cpp
Normal 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"
|
97
src/common/QskStippleMetrics.h
Normal file
97
src/common/QskStippleMetrics.h
Normal 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
327
src/nodes/QskLinesNode.cpp
Normal 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
52
src/nodes/QskLinesNode.h
Normal 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
|
@ -86,6 +86,8 @@ QskStrokeNode::QskStrokeNode()
|
||||
setMaterial( qskMaterialColorVertex );
|
||||
}
|
||||
|
||||
QskStrokeNode::~QskStrokeNode() = default;
|
||||
|
||||
void QskStrokeNode::setRenderHint( RenderHint renderHint )
|
||||
{
|
||||
Q_D( QskStrokeNode );
|
||||
|
@ -19,6 +19,7 @@ class QSK_EXPORT QskStrokeNode : public QSGGeometryNode
|
||||
{
|
||||
public:
|
||||
QskStrokeNode();
|
||||
~QskStrokeNode() override;
|
||||
|
||||
/*
|
||||
We only support monochrome pens ( QPen::color() ) and using a
|
||||
|
Loading…
x
Reference in New Issue
Block a user