642 lines
18 KiB
C++
642 lines
18 KiB
C++
/******************************************************************************
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
*****************************************************************************/
|
|
|
|
#include "QskBoxBorderColors.h"
|
|
#include "QskBoxBorderMetrics.h"
|
|
#include "QskBoxRenderer.h"
|
|
#include "QskBoxRendererColorMap.h"
|
|
#include "QskFunctions.h"
|
|
#include "QskGradient.h"
|
|
#include "QskVertex.h"
|
|
|
|
using namespace QskVertex;
|
|
|
|
namespace
|
|
{
|
|
class VRectIterator
|
|
{
|
|
public:
|
|
inline VRectIterator( const QskBoxRenderer::Quad& rect )
|
|
: m_rect( rect )
|
|
, m_value( rect.top )
|
|
{
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
line->setHLine( m_rect.left, m_rect.right, it.value(), it.color() );
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
line->setHLine( m_rect.left, m_rect.right, m_value, it.colorAt( m_value ) );
|
|
}
|
|
|
|
inline qreal value() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
inline bool advance()
|
|
{
|
|
if ( m_value == m_rect.top )
|
|
{
|
|
m_value = m_rect.bottom;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
const QskBoxRenderer::Quad& m_rect;
|
|
qreal m_value;
|
|
};
|
|
|
|
class HRectIterator
|
|
{
|
|
public:
|
|
inline HRectIterator( const QskBoxRenderer::Quad& rect )
|
|
: m_rect( rect )
|
|
, m_value( rect.left )
|
|
{
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
line->setVLine( it.value(), m_rect.top, m_rect.bottom, it.color() );
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
line->setVLine( m_value, m_rect.top, m_rect.bottom, it.colorAt( m_value ) );
|
|
}
|
|
|
|
inline qreal value() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
inline bool advance()
|
|
{
|
|
if ( m_value == m_rect.left )
|
|
{
|
|
m_value = m_rect.right;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
const QskBoxRenderer::Quad& m_rect;
|
|
qreal m_value;
|
|
};
|
|
|
|
class DSquareIterator
|
|
{
|
|
public:
|
|
inline DSquareIterator( const QskBoxRenderer::Quad& rect )
|
|
: m_rect( rect )
|
|
, m_step( 0 )
|
|
{
|
|
Q_ASSERT( rect.width == rect.height );
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
const auto v = it.value();
|
|
|
|
if ( v == 0.5 )
|
|
{
|
|
// this line is also a contour line, so we can skip it
|
|
return;
|
|
}
|
|
else if ( v < 0.5 )
|
|
{
|
|
const qreal dt = m_rect.width * 2 * v;
|
|
|
|
line->setLine( m_rect.left, m_rect.top + dt,
|
|
m_rect.left + dt, m_rect.top, it.color() );
|
|
}
|
|
else
|
|
{
|
|
const qreal dt = m_rect.width * 2 * ( v - 0.5 );
|
|
line->setLine( m_rect.left + dt, m_rect.bottom,
|
|
m_rect.right, m_rect.top + dt, it.color() );
|
|
}
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
const auto color = it.colorAt( value() );
|
|
const auto& r = m_rect;
|
|
|
|
switch ( m_step )
|
|
{
|
|
case 0:
|
|
{
|
|
line->setLine( r.left, r.top, r.left, r.top, color );
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
line->setLine( r.left, r.bottom, r.right, r.top, color );
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
line->setLine( r.right, r.bottom, r.right, r.bottom, color );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline qreal value() const
|
|
{
|
|
return m_step * 0.5;
|
|
}
|
|
|
|
inline bool advance()
|
|
{
|
|
return ++m_step <= 2;
|
|
}
|
|
|
|
private:
|
|
const QskBoxRenderer::Quad& m_rect;
|
|
int m_step;
|
|
};
|
|
|
|
class DRectIterator
|
|
{
|
|
public:
|
|
inline DRectIterator( const QskBoxRenderer::Quad& rect )
|
|
: m_rect( rect )
|
|
, m_step( 0 )
|
|
{
|
|
const qreal w = rect.width;
|
|
const qreal h = rect.height;
|
|
|
|
Q_ASSERT( w != h );
|
|
|
|
const qreal w2 = w * w;
|
|
const qreal h2 = h * h;
|
|
|
|
m_fx = ( w2 + h2 ) / w;
|
|
m_fy = ( w2 + h2 ) / h;
|
|
|
|
m_lx = m_rect.top - h2 / w;
|
|
m_ly = m_rect.left - w2 / h;
|
|
|
|
m_valueTR = w2 / ( w2 + h2 );
|
|
m_valueBL = h2 / ( w2 + h2 );
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
const auto v = it.value();
|
|
const auto color = it.color();
|
|
const auto& r = m_rect;
|
|
|
|
switch ( m_step )
|
|
{
|
|
case 1:
|
|
{
|
|
const qreal dx = v * m_fx;
|
|
const qreal dy = v * m_fy;
|
|
|
|
line->setLine( r.left, r.top + dy, r.left + dx, r.top, color );
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if ( r.width > r.height )
|
|
{
|
|
const qreal dx = v * m_fx;
|
|
line->setLine( m_lx + dx, r.bottom, r.left + dx, r.top, color );
|
|
}
|
|
else
|
|
{
|
|
const qreal dy = v * m_fy;
|
|
line->setLine( r.left, r.top + dy, r.right, m_ly + dy, color );
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
const qreal dx = v * m_fx;
|
|
const qreal dy = v * m_fy;
|
|
|
|
line->setLine( m_lx + dx, r.bottom, r.right, m_ly + dy, color );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template< class ColorIterator >
|
|
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
|
|
{
|
|
const auto& r = m_rect;
|
|
|
|
switch ( m_step )
|
|
{
|
|
case 0:
|
|
{
|
|
line->setLine( r.left, r.top,
|
|
r.left, r.top, it.colorAt( 0.0 ) );
|
|
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
if ( r.width >= r.height )
|
|
{
|
|
const qreal dx = m_valueBL * m_fx;
|
|
|
|
line->setLine( r.left, r.bottom,
|
|
r.left + dx, r.top, it.colorAt( m_valueBL ) );
|
|
}
|
|
else
|
|
{
|
|
const qreal dy = m_valueTR * m_fy;
|
|
|
|
line->setLine( r.left, r.top + dy,
|
|
r.right, r.top, it.colorAt( m_valueTR ) );
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if ( r.width >= r.height )
|
|
{
|
|
const qreal dx = m_valueTR * m_fx;
|
|
|
|
line->setLine( r.left + dx, r.bottom,
|
|
r.right, r.top, it.colorAt( m_valueTR ) );
|
|
}
|
|
else
|
|
{
|
|
const qreal dy = m_valueBL * m_fy;
|
|
|
|
line->setLine( r.left, r.bottom,
|
|
r.right, r.top + dy, it.colorAt( m_valueBL ) );
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
line->setLine( r.right, r.bottom,
|
|
r.right, r.bottom, it.colorAt( 1.0 ) );
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
Q_ASSERT( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
inline qreal value() const
|
|
{
|
|
switch ( m_step )
|
|
{
|
|
case 0:
|
|
return 0.0;
|
|
|
|
case 1:
|
|
return std::min( m_valueBL, m_valueTR );
|
|
|
|
case 2:
|
|
return std::max( m_valueBL, m_valueTR );
|
|
|
|
default:
|
|
return 1.0;
|
|
}
|
|
}
|
|
|
|
inline bool advance()
|
|
{
|
|
return ++m_step <= 3;
|
|
}
|
|
|
|
private:
|
|
const QskBoxRenderer::Quad& m_rect;
|
|
|
|
qreal m_fx, m_fy;
|
|
qreal m_lx, m_ly;
|
|
qreal m_valueTR, m_valueBL;
|
|
|
|
int m_step;
|
|
};
|
|
}
|
|
|
|
static inline void qskCreateFillOrdered( const QskBoxRenderer::Quad& rect,
|
|
const QskGradient& gradient, ColoredLine* line )
|
|
{
|
|
switch ( gradient.orientation() )
|
|
{
|
|
case QskGradient::Horizontal:
|
|
{
|
|
HRectIterator it( rect );
|
|
line = QskVertex::fillOrdered( it, rect.left, rect.right, gradient, line );
|
|
|
|
break;
|
|
}
|
|
case QskGradient::Vertical:
|
|
{
|
|
VRectIterator it( rect );
|
|
line = QskVertex::fillOrdered( it, rect.top, rect.bottom, gradient, line );
|
|
|
|
break;
|
|
}
|
|
case QskGradient::Diagonal:
|
|
{
|
|
if ( rect.width == rect.height )
|
|
{
|
|
DSquareIterator it( rect );
|
|
line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, line );
|
|
}
|
|
else
|
|
{
|
|
DRectIterator it( rect );
|
|
line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, line );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template< class ColorMap, class Line >
|
|
static inline void qskCreateFillRandom(
|
|
QskGradient::Orientation orientation,
|
|
const QskBoxRenderer::Quad& r, const ColorMap& map, Line* line )
|
|
{
|
|
if ( orientation == QskGradient::Vertical )
|
|
{
|
|
( line++ )->setLine( r.left, r.top, r.right, r.top, map.colorAt( 0.0 ) );
|
|
( line++ )->setLine( r.left, r.bottom, r.right, r.bottom, map.colorAt( 1.0 ) );
|
|
}
|
|
else
|
|
{
|
|
( line++ )->setLine( r.left, r.top, r.left, r.bottom, map.colorAt( 0.0 ) );
|
|
( line++ )->setLine( r.right, r.top, r.right, r.bottom, map.colorAt( 1.0 ) );
|
|
}
|
|
}
|
|
|
|
template< class Line >
|
|
static inline void qskCreateBorderMonochrome(
|
|
const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, QRgb rgb, Line* line )
|
|
{
|
|
qskCreateBorderMonochrome( out, in, Color( rgb ), line );
|
|
}
|
|
|
|
template< class Line >
|
|
static inline void qskCreateBorderMonochrome(
|
|
const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, Color color, Line* line )
|
|
{
|
|
auto l = line;
|
|
|
|
( l++ )->setLine( in.right, in.bottom, out.right, out.bottom, color );
|
|
( l++ )->setLine( in.left, in.bottom, out.left, out.bottom, color );
|
|
( l++ )->setLine( in.left, in.top, out.left, out.top, color );
|
|
( l++ )->setLine( in.right, in.top, out.right, out.top, color );
|
|
|
|
*l = line[ 0 ];
|
|
}
|
|
|
|
template< class Line >
|
|
static inline void qskCreateBorder(
|
|
const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in,
|
|
const QskBoxBorderColors& colors, Line* line )
|
|
{
|
|
const auto& gradientLeft = colors.left();
|
|
const auto& gradientRight = colors.right();
|
|
const auto& gradientTop = colors.top();
|
|
const auto& gradientBottom = colors.bottom();
|
|
|
|
// qdebug
|
|
|
|
const qreal dx1 = in.right - in.left;
|
|
const qreal dx2 = out.right - out.left;
|
|
const qreal dy1 = in.top - in.bottom;
|
|
const qreal dy2 = out.top - out.bottom;
|
|
|
|
for( const auto& stop : qAsConst( gradientBottom.stops() ) )
|
|
{
|
|
const Color c( stop.color() );
|
|
const qreal x1 = in.right - stop.position() * dx1;
|
|
const qreal x2 = out.right - stop.position() * dx2;
|
|
const qreal y1 = in.bottom;
|
|
const qreal y2 = out.bottom;
|
|
|
|
( line++ )->setLine( x1, y1, x2, y2, c );
|
|
}
|
|
|
|
for( const auto& stop : qAsConst( gradientLeft.stops() ) )
|
|
{
|
|
const Color c( stop.color() );
|
|
const qreal x1 = in.left;
|
|
const qreal x2 = out.left;
|
|
const qreal y1 = in.bottom + stop.position() * dy1;
|
|
const qreal y2 = out.bottom + stop.position() * dy2;
|
|
|
|
( line++ )->setLine( x1, y1, x2, y2, c );
|
|
}
|
|
|
|
for( const auto& stop : qAsConst( gradientTop.stops() ) )
|
|
{
|
|
const Color c( stop.color() );
|
|
const qreal x1 = in.left + stop.position() * dx1;
|
|
const qreal x2 = out.left + stop.position() * dx2;
|
|
const qreal y1 = in.top;
|
|
const qreal y2 = out.top;
|
|
|
|
( line++ )->setLine( x1, y1, x2, y2, c );
|
|
}
|
|
|
|
for( const auto& stop : qAsConst( gradientRight.stops() ) )
|
|
{
|
|
const Color c( stop.color() );
|
|
const qreal x1 = in.right;
|
|
const qreal x2 = out.right;
|
|
// ( 1 - stop.position() ) because we want to make the gradients go
|
|
// around the border clock-wise:
|
|
const qreal y1 = in.bottom + ( 1 - stop.position() ) * dy1;
|
|
const qreal y2 = out.bottom + ( 1 - stop.position() ) * dy2;
|
|
|
|
( line++ )->setLine( x1, y1, x2, y2, c );
|
|
}
|
|
}
|
|
|
|
void QskBoxRenderer::renderRectBorder(
|
|
const QRectF& rect, const QskBoxShapeMetrics& shape,
|
|
const QskBoxBorderMetrics& border, QSGGeometry& geometry )
|
|
{
|
|
Q_UNUSED( shape )
|
|
|
|
const Quad out = rect;
|
|
const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() );
|
|
|
|
if ( out == in )
|
|
{
|
|
allocateLines< Line >( geometry, 0 );
|
|
return;
|
|
}
|
|
|
|
const auto line = allocateLines< Line >( geometry, 4 + 1 );
|
|
qskCreateBorderMonochrome( out, in, Color(), line );
|
|
}
|
|
|
|
void QskBoxRenderer::renderRectFill(
|
|
const QRectF& rect, const QskBoxShapeMetrics& shape,
|
|
const QskBoxBorderMetrics& border, QSGGeometry& geometry )
|
|
{
|
|
Q_UNUSED( shape )
|
|
|
|
const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() );
|
|
|
|
if ( in.isEmpty() )
|
|
{
|
|
allocateLines< Line >( geometry, 0 );
|
|
return;
|
|
}
|
|
|
|
const auto line = allocateLines< Line >( geometry, 2 );
|
|
|
|
qskCreateFillRandom( QskGradient::Vertical,
|
|
in, ColorMapSolid( Color() ), line );
|
|
}
|
|
|
|
void QskBoxRenderer::renderRect(
|
|
const QRectF& rect, const QskBoxShapeMetrics& shape,
|
|
const QskBoxBorderMetrics& border, const QskBoxBorderColors& borderColors,
|
|
const QskGradient& gradient, QSGGeometry& geometry )
|
|
{
|
|
Q_UNUSED( shape )
|
|
|
|
const Quad out = rect;
|
|
const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() );
|
|
|
|
int fillLineCount = 0;
|
|
if ( !in.isEmpty() )
|
|
{
|
|
fillLineCount = gradient.stops().count();
|
|
|
|
if ( gradient.orientation() == QskGradient::Diagonal )
|
|
{
|
|
if ( in.width == in.height )
|
|
{
|
|
if ( !gradient.hasStopAt( 0.5 ) )
|
|
fillLineCount++;
|
|
}
|
|
else
|
|
{
|
|
// we might need extra lines for the corners
|
|
fillLineCount += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
int borderLineCount = 0;
|
|
if ( in != out )
|
|
{
|
|
const auto& bc = borderColors;
|
|
|
|
if ( bc.isVisible() )
|
|
{
|
|
// We can build a rectangular border from the 4 diagonal
|
|
// lines at the corners, but need an additional line
|
|
// for closing the border.
|
|
|
|
borderLineCount = 4 + 1;
|
|
|
|
if ( !bc.isMonochrome() )
|
|
{
|
|
// we might need extra lines to separate colors
|
|
// at the non closing corners
|
|
|
|
// ### As an optimization we could check orientation and colors
|
|
// to test whether colors are the same
|
|
const int additionalLines = -1
|
|
+ bc.left().stops().count() - 1
|
|
+ bc.top().stops().count() - 1
|
|
+ bc.right().stops().count() - 1
|
|
+ bc.bottom().stops().count() - 1;
|
|
|
|
borderLineCount += qMax( additionalLines, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
auto line = allocateLines< ColoredLine >( geometry, borderLineCount + fillLineCount );
|
|
|
|
if ( fillLineCount > 0 )
|
|
{
|
|
const auto& gd = gradient;
|
|
|
|
if ( gd.isMonochrome() )
|
|
{
|
|
const ColorMapSolid colorMap( gd.startColor() );
|
|
qskCreateFillRandom( QskGradient::Vertical, in, colorMap, line );
|
|
}
|
|
else
|
|
{
|
|
bool fillRandom = gd.stops().count() <= 2;
|
|
if ( fillRandom )
|
|
{
|
|
/*
|
|
Not necessarily a requirement for being ordered,
|
|
but we didn't implement a random fill algo for
|
|
diagonal gradients yet.
|
|
*/
|
|
fillRandom = gd.orientation() != QskGradient::Diagonal;
|
|
}
|
|
|
|
if ( fillRandom )
|
|
{
|
|
const ColorMapGradient colorMap( gd.startColor(), gd.endColor() );
|
|
qskCreateFillRandom( gd.orientation(), in, colorMap, line );
|
|
}
|
|
else
|
|
{
|
|
qskCreateFillOrdered( in, gd, line );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( borderLineCount > 0 )
|
|
{
|
|
const auto& bc = borderColors;
|
|
auto fillLines = line + fillLineCount;
|
|
|
|
if ( bc.isMonochrome() )
|
|
{
|
|
const auto rgb = bc.left().startColor().rgba();
|
|
qskCreateBorderMonochrome( rect, in, rgb, fillLines );
|
|
}
|
|
else
|
|
{
|
|
qskCreateBorder( rect, in, bc, fillLines );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QskBoxRenderer::renderRectFill( const QskBoxRenderer::Quad& rect,
|
|
const QskGradient& gradient, QskVertex::ColoredLine* line )
|
|
{
|
|
qskCreateFillOrdered( rect, gradient, line );
|
|
}
|