QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskBoxRenderer.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskBoxRenderer.h"
7#include "QskBoxShapeMetrics.h"
8#include "QskBoxBorderMetrics.h"
9#include "QskBoxBorderColors.h"
10#include "QskBoxMetrics.h"
11#include "QskBoxBasicStroker.h"
12#include "QskBoxGradientStroker.h"
13
14#include "QskGradient.h"
15#include "QskGradientDirection.h"
16#include "QskFunctions.h"
17
18#include <qsggeometry.h>
19
20static inline QskVertex::Line* qskAllocateLines(
21 QSGGeometry& geometry, int lineCount )
22{
23 geometry.allocate( 2 * lineCount ); // 2 points per line
24 return reinterpret_cast< QskVertex::Line* >( geometry.vertexData() );
25}
26
27static inline QskVertex::ColoredLine* qskAllocateColoredLines(
28 QSGGeometry& geometry, int lineCount )
29{
30 geometry.allocate( 2 * lineCount ); // 2 points per line
31 return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() );
32}
33
34static inline QskGradient qskEffectiveGradient(
35 const QRectF& rect, const QskGradient& gradient )
36{
37 if ( !gradient.isVisible() )
38 return gradient;
39
40 const auto dir = gradient.linearDirection();
41
42 auto g = gradient;
43
44 if ( !dir.isTilted() )
45 {
46 /*
47 Dealing with inverted gradient vectors makes the code even
48 more unreadable. So we simply invert stops/vector instead.
49 */
50 if ( ( dir.x1() > dir.x2() ) || ( dir.y1() > dir.y2() ) )
51 {
52 g.setLinearDirection( dir.x2(), dir.y2(), dir.x1(), dir.y1() );
53
54 if ( !g.isMonochrome() )
55 g.setStops( qskRevertedGradientStops( gradient.stops() ) );
56 }
57 }
58
59 if ( g.stretchMode() == QskGradient::StretchToSize )
60 g.stretchTo( rect );
61
62 return g;
63}
64
65static inline bool qskMaybeSpreading( const QskGradient& gradient )
66{
67 if ( gradient.stretchMode() == QskGradient::StretchToSize )
68 return !gradient.linearDirection().contains( QRectF( 0, 0, 1, 1 ) );
69
70 return true;
71}
72
73QskBoxRenderer::QskBoxRenderer( const QQuickWindow* window )
74 : m_window( window )
75{
76}
77
78QskBoxRenderer::~QskBoxRenderer()
79{
80}
81
82bool QskBoxRenderer::isGradientSupported( const QskGradient& gradient )
83{
84 if ( !gradient.isVisible() || gradient.isMonochrome() )
85 return true;
86
87 switch( gradient.type() )
88 {
89 case QskGradient::Stops:
90 {
91 // will be rendered as vertical linear gradient
92 return true;
93 }
94 case QskGradient::Linear:
95 {
96 if ( ( gradient.spreadMode() != QskGradient::PadSpread )
97 && qskMaybeSpreading( gradient ) )
98 {
99 // shouldn't be hard to implement the other spreadModes TODO ...
100 return false;
101 }
102
103 return true;
104 }
105
106 default:
107 {
108 /*
109 At least Conical gradients could be implemented easily TODO..
110
111 For the moment Radial/Conical gradients have to be done
112 with QskGradientMaterial
113 */
114 return false;
115 }
116 }
117
118 return false;
119}
120
121void QskBoxRenderer::setBorderLines(
122 const QRectF& rect, const QskBoxShapeMetrics& shape,
123 const QskBoxBorderMetrics& border, QSGGeometry& geometry )
124{
125 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
126 geometry.markVertexDataDirty();
127
128 const QskBoxMetrics metrics( rect, shape, border );
129 const QskBoxBasicStroker stroker( metrics );
130
131 const auto lines = qskAllocateLines( geometry, stroker.borderCount() );
132 if ( lines )
133 stroker.setBorderLines( lines );
134}
135
136void QskBoxRenderer::setFillLines(
137 const QRectF& rect, const QskBoxShapeMetrics& shape, QSGGeometry& geometry )
138{
139 setFillLines( rect, shape, QskBoxBorderMetrics(), geometry );
140}
141
142void QskBoxRenderer::setFillLines(
143 const QRectF& rect, const QskBoxShapeMetrics& shape,
144 const QskBoxBorderMetrics& border, QSGGeometry& geometry )
145{
146 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
147 geometry.markVertexDataDirty();
148
149 const QskBoxMetrics metrics( rect, shape, border );
150 QskBoxBasicStroker stroker( metrics );
151
152 if ( auto lines = qskAllocateLines( geometry, stroker.fillCount() ) )
153 stroker.setFillLines( lines );
154}
155
156void QskBoxRenderer::setColoredFillLines( const QRectF& rect,
157 const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border,
158 const QskGradient& gradient, QSGGeometry& geometry )
159{
160 setColoredBorderAndFillLines( rect, shape, border,
161 QskBoxBorderColors(), gradient, geometry );
162}
163
164void QskBoxRenderer::setColoredBorderLines( const QRectF& rect,
165 const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border,
166 const QskBoxBorderColors& borderColors, QSGGeometry& geometry )
167{
168 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
169 geometry.markVertexDataDirty();
170
171 const QskBoxBasicStroker stroker( QskBoxMetrics( rect, shape, border ), borderColors );
172
173 if ( auto lines = qskAllocateColoredLines( geometry, stroker.borderCount() ) )
174 stroker.setBoxLines( lines, nullptr );
175}
176
177void QskBoxRenderer::setColoredBorderAndFillLines( const QRectF& rect,
178 const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border,
179 const QskBoxBorderColors& borderColors, const QskGradient& gradient,
180 QSGGeometry& geometry )
181{
182 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
183 geometry.markVertexDataDirty();
184
185 const QskBoxMetrics metrics( rect, shape, border );
186 const auto effectiveGradient = qskEffectiveGradient( metrics.innerRect, gradient );
187
188 if ( metrics.innerRect.isEmpty() ||
189 QskVertex::ColorMap::isGradientSupported( effectiveGradient, metrics.innerRect ) )
190 {
191 /*
192 The gradient can be translated to a QskBoxRenderer::ColorMap and we can do all
193 coloring by adding a color info to points of the contour lines.
194 The orientation of contour lines does not depend on the direction
195 of the gradient vector.
196
197 This allows using simpler and faster algos.
198 */
199
200 const QskBoxBasicStroker stroker( metrics, borderColors, effectiveGradient );
201
202 const int fillCount = stroker.fillCount();
203 const int borderCount = stroker.borderCount();
204
205 if ( auto lines = qskAllocateColoredLines( geometry, borderCount + fillCount ) )
206 {
207 auto fillLines = fillCount ? lines : nullptr;
208 auto borderLines = borderCount ? lines + fillCount : nullptr;
209
210 stroker.setBoxLines( borderLines, fillLines );
211 }
212 }
213 else
214 {
215 /*
216 We need to create gradient and contour lines in the correct order
217 perpendicular to the gradient vector.
218 */
219 const QskBoxBasicStroker borderStroker( metrics, borderColors );
220 QskBoxGradientStroker fillStroker( metrics, effectiveGradient );
221
222 const int fillCount = fillStroker.lineCount();
223 const int borderCount = borderStroker.borderCount();
224 const int extraLine = ( fillCount && borderCount ) ? 1 : 0;
225
226 auto lines = qskAllocateColoredLines(
227 geometry, fillCount + borderCount + extraLine );
228
229 if ( fillCount )
230 fillStroker.setLines( fillCount, lines );
231
232 if ( borderCount )
233 borderStroker.setBorderLines( lines + fillCount + extraLine );
234
235 if ( extraLine )
236 {
237 // dummy line to connect filling and border
238 const auto l = lines + fillCount;
239 l[0].p1 = l[-1].p2;
240 l[0].p2 = l[+1].p1;
241 }
242 }
243}
244
245QskGradient QskBoxRenderer::effectiveGradient( const QskGradient& gradient )
246{
247 if ( ( gradient.type() == QskGradient::Stops ) || gradient.isMonochrome() )
248 {
249 // the shader for linear gradients is the fastest
250
251 auto g = gradient;
252 g.setDirection( QskGradient::Linear );
253
254 return g;
255 }
256
257 return gradient;
258}