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 QskBoxMetrics metrics( rect, shape, border );
172 const QskBoxBasicStroker stroker( metrics, borderColors );
173
174 if ( auto lines = qskAllocateColoredLines( geometry, stroker.borderCount() ) )
175 stroker.setBoxLines( lines, nullptr );
176}
177
178void QskBoxRenderer::setColoredBorderAndFillLines( const QRectF& rect,
179 const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border,
180 const QskBoxBorderColors& borderColors, const QskGradient& gradient,
181 QSGGeometry& geometry )
182{
183 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
184 geometry.markVertexDataDirty();
185
186 const QskBoxMetrics metrics( rect, shape, border );
187 const auto effectiveGradient = qskEffectiveGradient( metrics.innerRect, gradient );
188
189 if ( metrics.innerRect.isEmpty() ||
190 QskVertex::ColorMap::isGradientSupported( effectiveGradient, metrics.innerRect ) )
191 {
192 /*
193 The gradient can be translated to a QskBoxRenderer::ColorMap and we can do all
194 coloring by adding a color info to points of the contour lines.
195 The orientation of contour lines does not depend on the direction
196 of the gradient vector.
197
198 This allows using simpler and faster algos.
199 */
200
201 const QskBoxBasicStroker stroker( metrics, borderColors, effectiveGradient );
202
203 const int fillCount = stroker.fillCount();
204 const int borderCount = stroker.borderCount();
205
206 if ( auto lines = qskAllocateColoredLines( geometry, borderCount + fillCount ) )
207 {
208 auto fillLines = fillCount ? lines : nullptr;
209 auto borderLines = borderCount ? lines + fillCount : nullptr;
210
211 stroker.setBoxLines( borderLines, fillLines );
212 }
213 }
214 else
215 {
216 /*
217 We need to create gradient and contour lines in the correct order
218 perpendicular to the gradient vector.
219 */
220 const QskBoxBasicStroker borderStroker( metrics, borderColors );
221 QskBoxGradientStroker fillStroker( metrics, effectiveGradient );
222
223 const int fillCount = fillStroker.lineCount();
224 const int borderCount = borderStroker.borderCount();
225 const int extraLine = ( fillCount && borderCount ) ? 1 : 0;
226
227 auto lines = qskAllocateColoredLines(
228 geometry, fillCount + borderCount + extraLine );
229
230 if ( fillCount )
231 fillStroker.setLines( fillCount, lines );
232
233 if ( borderCount )
234 borderStroker.setBorderLines( lines + fillCount + extraLine );
235
236 if ( extraLine )
237 {
238 // dummy line to connect filling and border
239 const auto l = lines + fillCount;
240 l[0].p1 = l[-1].p2;
241 l[0].p2 = l[+1].p1;
242 }
243 }
244}
245
246QskGradient QskBoxRenderer::effectiveGradient( const QskGradient& gradient )
247{
248 if ( ( gradient.type() == QskGradient::Stops ) || gradient.isMonochrome() )
249 {
250 // the shader for linear gradients is the fastest
251
252 auto g = gradient;
253 g.setDirection( QskGradient::Linear );
254
255 return g;
256 }
257
258 return gradient;
259}