QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskLayoutEngine2D.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskLayoutEngine2D.h"
7#include "QskLayoutChain.h"
8#include "QskLayoutElement.h"
9#include "QskFunctions.h"
10
11#include <qguiapplication.h>
12
13namespace
14{
15 class LayoutData
16 {
17 public:
18
19 QRectF geometryAt( const QRect& grid ) const
20 {
21 const auto x1 = columns[ grid.left() ].start;
22 const auto x2 = columns[ grid.right() ].end();
23 const auto y1 = rows[ grid.top() ].start;
24 const auto y2 = rows[ grid.bottom() ].end();
25
26 return QRectF( rect.x() + x1, rect.y() + y1, x2 - x1, y2 - y1 );
27 }
28
29 Qt::LayoutDirection direction;
30
31 QRectF rect;
32 QskLayoutChain::Segments rows;
33 QskLayoutChain::Segments columns;
34 };
35}
36
37class QskLayoutEngine2D::PrivateData
38{
39 public:
40 PrivateData()
41 : defaultAlignment( Qt::AlignLeft | Qt::AlignVCenter )
42 , extraSpacingAt( 0 )
43 , visualDirection( Qt::LeftToRight )
44 , constraintType( -1 )
45 , blockInvalidate( false )
46 {
47 }
48
49 inline QskLayoutChain& layoutChain( Qt::Orientation orientation )
50 {
51 return ( orientation == Qt::Horizontal ) ? columnChain : rowChain;
52 }
53
54 inline Qt::Alignment effectiveAlignment( Qt::Alignment alignment ) const
55 {
56 const auto align = static_cast< Qt::Alignment >( defaultAlignment );
57
58 if ( !( alignment & Qt::AlignVertical_Mask ) )
59 alignment |= ( align & Qt::AlignVertical_Mask );
60
61 if ( !( alignment & Qt::AlignHorizontal_Mask ) )
62 alignment |= ( align & Qt::AlignHorizontal_Mask );
63
64 return alignment;
65 }
66
67 QskLayoutChain columnChain;
68 QskLayoutChain rowChain;
69
70 QSizeF layoutSize;
71
72 QskLayoutChain::Segments rows;
73 QskLayoutChain::Segments columns;
74
75 const LayoutData* layoutData = nullptr;
76
77 unsigned int defaultAlignment : 8;
78 unsigned int extraSpacingAt : 4;
79 unsigned int visualDirection : 4;
80
81 int constraintType : 3;
82
83 /*
84 Some weired controls do lazy updates inside of their sizeHint calculation
85 that lead to LayoutRequest events. While being in the process of
86 updating the tables we can't - and don't need to - handle invalidations
87 because of them.
88 */
89 bool blockInvalidate : 1;
90};
91
92QskLayoutEngine2D::QskLayoutEngine2D()
93 : m_data( new PrivateData )
94{
95 m_data->columnChain.setSpacing( defaultSpacing( Qt::Horizontal ) );
96 m_data->rowChain.setSpacing( defaultSpacing( Qt::Vertical ) );
97}
98
99QskLayoutEngine2D::~QskLayoutEngine2D()
100{
101}
102
103bool QskLayoutEngine2D::setVisualDirection( Qt::LayoutDirection direction )
104{
105 if ( m_data->visualDirection != direction )
106 {
107 m_data->visualDirection = direction;
108 return true;
109 }
110
111 return false;
112}
113
114Qt::LayoutDirection QskLayoutEngine2D::visualDirection() const
115{
116 return static_cast< Qt::LayoutDirection >( m_data->visualDirection );
117}
118
119bool QskLayoutEngine2D::setDefaultAlignment( Qt::Alignment alignment )
120{
121 if ( defaultAlignment() != alignment )
122 {
123 m_data->defaultAlignment = alignment;
124 return true;
125 }
126
127 return false;
128}
129
130Qt::Alignment QskLayoutEngine2D::defaultAlignment() const
131{
132 return static_cast< Qt::Alignment >( m_data->defaultAlignment );
133}
134
135qreal QskLayoutEngine2D::defaultSpacing( Qt::Orientation ) const
136{
137 return 5.0; // should be from the skin
138}
139
140bool QskLayoutEngine2D::setSpacing(
141 qreal spacing, Qt::Orientations orientations )
142{
143 if ( spacing < 0.0 )
144 spacing = 0.0;
145
146 bool isModified = false;
147
148 for ( auto o : { Qt::Horizontal, Qt::Vertical } )
149 {
150 if ( orientations & o )
151 isModified |= m_data->layoutChain( o ).setSpacing( spacing );
152 }
153
154 if ( isModified )
155 invalidate( LayoutCache );
156
157 return isModified;
158}
159
160qreal QskLayoutEngine2D::spacing( Qt::Orientation orientation ) const
161{
162 return m_data->layoutChain( orientation ).spacing();
163}
164
165bool QskLayoutEngine2D::setExtraSpacingAt( Qt::Edges edges )
166{
167 if ( edges == extraSpacingAt() )
168 return false;
169
170 m_data->extraSpacingAt = edges;
171
172 {
173 int fillMode = 0;
174
175 if ( edges & Qt::LeftEdge )
176 fillMode |= QskLayoutChain::Leading;
177
178 if ( edges & Qt::RightEdge )
179 fillMode |= QskLayoutChain::Trailing;
180
181 m_data->columnChain.setFillMode( fillMode );
182 }
183
184 {
185 int fillMode = 0;
186
187 if ( edges & Qt::TopEdge )
188 fillMode |= QskLayoutChain::Leading;
189
190 if ( edges & Qt::BottomEdge )
191 fillMode |= QskLayoutChain::Trailing;
192
193 m_data->rowChain.setFillMode( fillMode );
194 }
195
196 m_data->layoutSize = QSize();
197 m_data->rows.clear();
198 m_data->columns.clear();
199
200 return true;
201}
202
203Qt::Edges QskLayoutEngine2D::extraSpacingAt() const
204{
205 return static_cast< Qt::Edges >( m_data->extraSpacingAt );
206}
207
208void QskLayoutEngine2D::setGeometries( const QRectF& rect )
209{
210 if ( rowCount() < 1 || columnCount() < 1 )
211 return;
212
213 if ( m_data->layoutSize != rect.size() )
214 {
215 m_data->layoutSize = rect.size();
216 updateSegments( rect.size() );
217 }
218
219 /*
220 In case we have items that send LayoutRequest events on
221 geometry changes - what doesn't make much sense - we
222 better make a ( implicitely shared ) copy of the rows/columns.
223 */
224 LayoutData data;
225 data.rows = m_data->rows;
226 data.columns = m_data->columns;
227 data.rect = rect;
228
229 data.direction = visualDirection();
230 if ( data.direction == Qt::LayoutDirectionAuto )
231 data.direction = QGuiApplication::layoutDirection();
232
233 m_data->layoutData = &data;
234 layoutItems();
235 m_data->layoutData = nullptr;
236}
237
238QRectF QskLayoutEngine2D::geometryAt(
239 const QskLayoutElement* element, const QRect& grid ) const
240{
241 const auto layoutData = m_data->layoutData;
242
243 if ( layoutData && element )
244 {
245 auto rect = layoutData->geometryAt( grid );
246
247 const auto size = element->constrainedSize( rect.size() );
248 const auto alignment = m_data->effectiveAlignment( element->alignment() );
249
250 rect = qskAlignedRectF( rect, size, alignment );
251
252 if ( layoutData->direction == Qt::RightToLeft )
253 {
254 const auto& r = layoutData->rect;
255 rect.moveRight( r.right() - ( rect.left() - r.left() ) );
256 }
257
258 return rect;
259 }
260
261 return QRectF( 0, 0, -1, -1 );
262}
263
264qreal QskLayoutEngine2D::widthForHeight( qreal height ) const
265{
266 const QSizeF constraint( -1, height );
267 return sizeHint( Qt::PreferredSize, constraint ).width();
268}
269
270qreal QskLayoutEngine2D::heightForWidth( qreal width ) const
271{
272 const QSizeF constraint( width, -1 );
273 return sizeHint( Qt::PreferredSize, constraint ).height();
274}
275
276QSizeF QskLayoutEngine2D::sizeHint(
277 Qt::SizeHint which, const QSizeF& constraint ) const
278{
279 if ( constraint.isValid() )
280 return constraint; // should never happen
281
282 if ( effectiveCount( Qt::Horizontal ) <= 0 )
283 return QSizeF( 0.0, 0.0 );
284
285 auto requestType = constraintType();
286
287 switch ( requestType )
288 {
289 case QskSizePolicy::HeightForWidth:
290 {
291 if ( constraint.height() >= 0 )
292 {
293 qWarning( "QskLayoutEngine2D: HeightForWidth conflict." );
294 return QSizeF();
295 }
296
297 if ( constraint.width() <= 0 )
298 requestType = QskSizePolicy::Unconstrained;
299
300 break;
301 }
302 case QskSizePolicy::WidthForHeight:
303 {
304 if ( constraint.width() >= 0 )
305 {
306 qWarning( "QskLayoutEngine2D: WidthForHeight conflict." );
307 return QSizeF();
308 }
309
310 if ( constraint.height() <= 0 )
311 requestType = QskSizePolicy::Unconstrained;
312
313 break;
314 }
315 default:
316 {
317 if ( constraint.height() >= 0 || constraint.width() >= 0 )
318 {
319 /*
320 As none of the elements has the Constrained flag the constraint
321 will have no effect and we ignore it to make use of the cached
322 results from unconstrained requests
323 */
324#if 0
325 qWarning( "QskLayoutEngine2D: constraint will be ignored." );
326#endif
327 }
328 }
329 }
330
331 auto& rowChain = m_data->rowChain;
332 auto& columnChain = m_data->columnChain;
333
334 m_data->blockInvalidate = true;
335
336 switch( requestType )
337 {
338 case QskSizePolicy::HeightForWidth:
339 {
340 setupChain( Qt::Horizontal );
341
342 const auto constraints = columnChain.segments( constraint.width() );
343 setupChain( Qt::Vertical, constraints );
344
345 break;
346 }
347 case QskSizePolicy::WidthForHeight:
348 {
349 setupChain( Qt::Vertical );
350
351 const auto constraints = rowChain.segments( constraint.height() );
352 setupChain( Qt::Horizontal, constraints );
353
354 break;
355 }
356 default:
357 {
358 setupChain( Qt::Horizontal );
359 setupChain( Qt::Vertical );
360 }
361 }
362
363 m_data->blockInvalidate = false;
364
365 QSizeF hint;
366
367 if ( constraint.width() <= 0.0 )
368 hint.rwidth() = columnChain.boundingMetrics().metric( which );
369
370 if ( constraint.height() <= 0.0 )
371 hint.rheight() = rowChain.boundingMetrics().metric( which );
372
373 return hint;
374}
375
376void QskLayoutEngine2D::setupChain( Qt::Orientation orientation ) const
377{
378 setupChain( orientation, QskLayoutChain::Segments() );
379}
380
381void QskLayoutEngine2D::setupChain( Qt::Orientation orientation,
382 const QskLayoutChain::Segments& constraints ) const
383{
384 const auto count = effectiveCount( orientation );
385 const qreal constraint =
386 constraints.isEmpty() ? -1.0 : constraints.last().end();
387
388 auto& chain = m_data->layoutChain( orientation );
389
390 if ( ( chain.constraint() == constraint ) && ( chain.count() == count ) )
391 {
392 return; // already up to date
393 }
394
395 chain.reset( count, constraint );
396 setupChain( orientation, constraints, chain );
397 chain.finish();
398
399#if 0
400 qDebug() << "==" << this << orientation << chain.count();
401
402 for ( int i = 0; i < chain.count(); i++ )
403 qDebug() << i << ":" << chain.cell( i );
404#endif
405}
406
407void QskLayoutEngine2D::updateSegments( const QSizeF& size ) const
408{
409 auto& rowChain = m_data->rowChain;
410 auto& columnChain = m_data->columnChain;
411
412 auto& rows = m_data->rows;
413 auto& columns = m_data->columns;
414
415 m_data->blockInvalidate = true;
416
417 switch( constraintType() )
418 {
419 case QskSizePolicy::WidthForHeight:
420 {
421 setupChain( Qt::Vertical );
422 rows = rowChain.segments( size.height() );
423
424 setupChain( Qt::Horizontal, rows );
425 columns = columnChain.segments( size.width() );
426
427 break;
428 }
429 case QskSizePolicy::HeightForWidth:
430 {
431 setupChain( Qt::Horizontal );
432 columns = columnChain.segments( size.width() );
433
434 setupChain( Qt::Vertical, m_data->columns );
435 rows = rowChain.segments( size.height() );
436
437 break;
438 }
439 default:
440 {
441 setupChain( Qt::Horizontal );
442 columns = columnChain.segments( size.width() );
443
444 setupChain( Qt::Vertical );
445 rows = rowChain.segments( size.height() );
446 }
447 }
448
449 m_data->blockInvalidate = false;
450}
451
452void QskLayoutEngine2D::invalidate( int what )
453{
454 if ( m_data->blockInvalidate )
455 return;
456
457 if ( what & ElementCache )
458 {
459 m_data->constraintType = -1;
460 invalidateElementCache();
461 }
462
463 if ( what & LayoutCache )
464 {
465 m_data->rowChain.invalidate();
466 m_data->columnChain.invalidate();
467
468 m_data->layoutSize = QSize();
469 m_data->rows.clear();
470 m_data->columns.clear();
471 }
472}
473
474QskSizePolicy::ConstraintType QskLayoutEngine2D::constraintType() const
475{
476 if ( m_data->constraintType < 0 )
477 {
478 auto constraintType = QskSizePolicy::Unconstrained;
479
480 for ( int i = 0; i < count(); i++ )
481 {
482 const auto type = sizePolicyAt( i ).constraintType();
483
484 if ( type != QskSizePolicy::Unconstrained )
485 {
486 if ( constraintType == QskSizePolicy::Unconstrained )
487 {
488 constraintType = type;
489 }
490 else if ( constraintType != type )
491 {
492 qWarning( "QskLayoutEngine2D: conflicting constraints");
493 constraintType = QskSizePolicy::Unconstrained;
494 }
495 }
496 }
497
498 m_data->constraintType = constraintType;
499 }
500
501 return static_cast< QskSizePolicy::ConstraintType >( m_data->constraintType );
502}
@ LeftToRight