qskinny/src/nodes/QskBoxRenderer.cpp

216 lines
6.6 KiB
C++
Raw Normal View History

2022-12-06 15:52:55 +00:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
2023-04-06 07:23:37 +00:00
* SPDX-License-Identifier: BSD-3-Clause
2022-12-06 15:52:55 +00:00
*****************************************************************************/
#include "QskBoxRenderer.h"
#include "QskBoxShapeMetrics.h"
2023-01-11 11:51:16 +00:00
#include "QskBoxBorderMetrics.h"
#include "QskBoxBorderColors.h"
#include "QskBoxMetrics.h"
#include "QskBoxBasicStroker.h"
#include "QskBoxGradientStroker.h"
2022-12-06 15:52:55 +00:00
#include "QskGradient.h"
#include "QskGradientDirection.h"
#include "QskFunctions.h"
2022-12-06 15:52:55 +00:00
#include <qsggeometry.h>
static inline QskVertex::Line* qskAllocateLines(
QSGGeometry& geometry, int lineCount )
{
geometry.allocate( 2 * lineCount ); // 2 points per line
return reinterpret_cast< QskVertex::Line* >( geometry.vertexData() );
}
static inline QskVertex::ColoredLine* qskAllocateColoredLines(
QSGGeometry& geometry, int lineCount )
{
geometry.allocate( 2 * lineCount ); // 2 points per line
return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() );
}
static inline QskGradient qskEffectiveGradient(
const QRectF& rect, const QskGradient& gradient )
{
const auto dir = gradient.linearDirection();
auto g = gradient;
if ( !dir.isTilted() )
{
/*
Dealing with inverted gradient vectors makes the code even
more unreadable. So we simply invert stops/vector instead.
*/
if ( ( dir.x1() > dir.x2() ) || ( dir.y1() > dir.y2() ) )
{
g.setLinearDirection( dir.x2(), dir.y2(), dir.x1(), dir.y1() );
if ( !g.isMonochrome() )
g.setStops( qskRevertedGradientStops( gradient.stops() ) );
}
}
if ( g.stretchMode() == QskGradient::StretchToSize )
g.stretchTo( rect );
return g;
}
static inline bool qskMaybeSpreading( const QskGradient& gradient )
{
if ( gradient.stretchMode() == QskGradient::StretchToSize )
return !gradient.linearDirection().contains( QRectF( 0, 0, 1, 1 ) );
return true;
}
bool QskBox::isGradientSupported(
const QskBoxShapeMetrics&, const QskGradient& gradient )
{
if ( !gradient.isVisible() || gradient.isMonochrome() )
return true;
switch( gradient.type() )
{
case QskGradient::Stops:
{
// will be rendered as vertical linear gradient
return true;
}
case QskGradient::Linear:
{
if ( ( gradient.spreadMode() != QskGradient::PadSpread )
&& qskMaybeSpreading( gradient ) )
{
// shouldn't be hard to implement the other spreadModes TODO ...
return false;
}
return true;
}
default:
{
/*
At least Conical gradients could be implemented easily TODO..
For the moment Radial/Conical gradients have to be done
with QskGradientMaterial
*/
return false;
}
}
return false;
}
void QskBox::renderBorderGeometry(
2022-12-06 15:52:55 +00:00
const QRectF& rect, const QskBoxShapeMetrics& shape,
const QskBoxBorderMetrics& border, QSGGeometry& geometry )
{
2023-01-11 11:51:16 +00:00
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
const QskBoxMetrics metrics( rect, shape, border );
const QskBoxBasicStroker stroker( metrics );
const auto lines = qskAllocateLines( geometry, stroker.borderCount() );
if ( lines )
stroker.setBorderLines( lines );
2023-01-11 11:51:16 +00:00
}
void QskBox::renderFillGeometry(
2023-01-11 11:51:16 +00:00
const QRectF& rect, const QskBoxShapeMetrics& shape, QSGGeometry& geometry )
{
renderFillGeometry( rect, shape, QskBoxBorderMetrics(), geometry );
2022-12-06 15:52:55 +00:00
}
void QskBox::renderFillGeometry(
2022-12-06 15:52:55 +00:00
const QRectF& rect, const QskBoxShapeMetrics& shape,
const QskBoxBorderMetrics& border, QSGGeometry& geometry )
{
2023-01-11 11:51:16 +00:00
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
const QskBoxMetrics metrics( rect, shape, border );
QskBoxBasicStroker stroker( metrics );
if ( auto lines = qskAllocateLines( geometry, stroker.fillCount() ) )
stroker.setFillLines( lines );
2023-01-11 11:51:16 +00:00
}
void QskBox::renderBox( const QRectF& rect,
2023-01-11 11:51:16 +00:00
const QskBoxShapeMetrics& shape, const QskGradient& gradient,
QSGGeometry& geometry )
{
renderBox( rect, shape, QskBoxBorderMetrics(),
QskBoxBorderColors(), gradient, geometry );
2022-12-06 15:52:55 +00:00
}
void QskBox::renderBox( const QRectF& rect,
2022-12-06 15:52:55 +00:00
const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border,
const QskBoxBorderColors& borderColors, const QskGradient& gradient,
QSGGeometry& geometry )
{
2023-01-11 11:51:16 +00:00
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
const QskBoxMetrics metrics( rect, shape, border );
const auto effectiveGradient = qskEffectiveGradient( metrics.innerRect, gradient );
if ( metrics.innerRect.isEmpty() ||
QskBox::ColorMap::isGradientSupported( effectiveGradient, metrics.innerRect ) )
{
/*
The gradient can be translated to a QskBox::ColorMap and we can do all
coloring by adding a color info to points of the contour lines.
The orientation of contour lines does not depend on the direction
of the gradient vector.
This allows using simpler and faster algos.
*/
const QskBoxBasicStroker stroker( metrics, borderColors, effectiveGradient );
const int fillCount = stroker.fillCount();
const int borderCount = stroker.borderCount();
auto lines = qskAllocateColoredLines( geometry, borderCount + fillCount );
auto fillLines = fillCount ? lines : nullptr;
auto borderLines = borderCount ? lines + fillCount : nullptr;
stroker.setBoxLines( borderLines, fillLines );
}
2022-12-06 15:52:55 +00:00
else
{
/*
We need to create gradient and contour lines in the correct order
perpendicular to the gradient vector.
*/
const QskBoxBasicStroker borderStroker( metrics, borderColors );
QskBoxGradientStroker fillStroker( metrics, effectiveGradient );
2022-12-06 15:52:55 +00:00
const int fillCount = fillStroker.lineCount();
const int borderCount = borderStroker.borderCount();
const int extraLine = ( fillCount && borderCount ) ? 1 : 0;
auto lines = qskAllocateColoredLines(
geometry, fillCount + borderCount + extraLine );
2023-01-04 15:15:22 +00:00
if ( fillCount )
fillStroker.setLines( fillCount, lines );
2023-01-04 15:15:22 +00:00
if ( borderCount )
borderStroker.setBorderLines( lines + fillCount + extraLine );
2022-12-10 15:08:32 +00:00
if ( extraLine )
{
// dummy line to connect filling and border
const auto l = lines + fillCount;
l[0].p1 = l[-1].p2;
l[0].p2 = l[+1].p1;
2022-12-06 15:52:55 +00:00
}
}
}