QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskLayoutChain.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskLayoutChain.h"
7
8#include <qvarlengtharray.h>
9#include <qvector.h>
10#include <qdebug.h>
11
12#include <cmath>
13
14QskLayoutChain::QskLayoutChain()
15{
16}
17
18QskLayoutChain::~QskLayoutChain()
19{
20}
21
22void QskLayoutChain::invalidate()
23{
24 m_cells.clear();
25 m_constraint = -2;
26}
27
28void QskLayoutChain::reset( int count, qreal constraint )
29{
30 m_cells.fill( CellData(), count );
31 m_constraint = constraint;
32 m_sumStretches = 0;
33 m_validCells = 0;
34}
35
36void QskLayoutChain::shrinkCell( int index, const CellData& newCell )
37{
38 if ( !newCell.isValid )
39 return;
40
41 auto& cell = m_cells[ index ];
42
43 if ( !cell.isValid )
44 {
45 cell = newCell;
46 cell.stretch = qMax( cell.stretch, 0 );
47 m_validCells++;
48 }
49 else
50 {
51 cell.canGrow &= newCell.canGrow;
52 if ( newCell.stretch >= 0 )
53 cell.stretch = qMax( cell.stretch, newCell.stretch );
54
55 if ( !newCell.metrics.isDefault() )
56 {
57 auto& metrics = cell.metrics;
58 auto& newMetrics = newCell.metrics;
59
60 metrics.setMinimum( qMax( metrics.minimum(), newMetrics.minimum() ) );
61 metrics.setPreferred( qMax( metrics.preferred(), newMetrics.preferred() ) );
62
63 if ( newMetrics.maximum() < metrics.maximum() )
64 {
65 metrics.setMaximum( newMetrics.maximum() );
66 cell.isShrunk = true;
67 }
68
69 cell.metrics.normalize();
70 }
71 }
72}
73
74void QskLayoutChain::expandCell( int index, const CellData& newCell )
75{
76 if ( !newCell.isValid )
77 return;
78
79 auto& cell = m_cells[ index ];
80
81 if ( !cell.isValid )
82 {
83 cell = newCell;
84 m_validCells++;
85 }
86 else
87 {
88 cell.canGrow |= newCell.canGrow;
89 cell.stretch = qMax( cell.stretch, newCell.stretch );
90
91 cell.metrics.setMetrics(
92 qMax( cell.metrics.minimum(), newCell.metrics.minimum() ),
93 qMax( cell.metrics.preferred(), newCell.metrics.preferred() ),
94 qMax( cell.metrics.maximum(), newCell.metrics.maximum() )
95 );
96 }
97}
98
99void QskLayoutChain::expandCells(
100 int index, int count, const CellData& multiCell )
101{
102 QskLayoutChain chain;
103 chain.setSpacing( m_spacing );
104 chain.reset( count, -1 );
105
106 for ( int i = 0; i < count; i++ )
107 {
108 auto& cell = chain.m_cells[ i ];
109 cell = m_cells[ index + i ];
110
111 if ( !cell.isValid )
112 {
113 cell.isValid = true;
114 cell.canGrow = multiCell.canGrow;
115 cell.stretch = multiCell.stretch;
116 }
117 }
118 chain.finish();
119
120 QskLayoutChain::Segments minimum;
121 QskLayoutChain::Segments preferred;
122 QskLayoutChain::Segments maximum;
123
124 const auto chainMetrics = chain.boundingMetrics();
125
126 if ( multiCell.metrics.minimum() > chainMetrics.minimum() )
127 minimum = chain.segments( multiCell.metrics.minimum() );
128
129 if ( multiCell.metrics.preferred() > chainMetrics.preferred() )
130 preferred = chain.segments( multiCell.metrics.preferred() );
131
132 if ( chainMetrics.maximum() == QskLayoutMetrics::unlimited )
133 {
134 if ( multiCell.metrics.maximum() < QskLayoutMetrics::unlimited )
135 maximum = chain.segments( multiCell.metrics.maximum() );
136 }
137
138 for ( int i = 0; i < count; i++ )
139 {
140 auto& cell = m_cells[ index + i ];
141
142 cell.canGrow |= multiCell.canGrow;
143 cell.stretch = qMax( cell.stretch, multiCell.stretch );
144
145 if ( !minimum.isEmpty() )
146 cell.metrics.expandMinimum( minimum[i].length );
147
148 if ( !preferred.isEmpty() )
149 cell.metrics.expandPreferred( preferred[i].length );
150
151 if ( !maximum.isEmpty() && !cell.isValid )
152 cell.metrics.setMaximum( maximum[i].length );
153
154 cell.metrics.normalize();
155
156 if ( !cell.isValid )
157 {
158 cell.isValid = true;
159 m_validCells++;
160 }
161 }
162}
163
164void QskLayoutChain::finish()
165{
166 qreal minimum = 0.0;
167 qreal preferred = 0.0;
168 qreal maximum = 0.0;
169
170 m_sumStretches = 0;
171 m_validCells = 0;
172
173 if ( !m_cells.empty() )
174 {
175 const auto maxMaximum = QskLayoutMetrics::unlimited;
176
177 for ( auto& cell : m_cells )
178 {
179 if ( !cell.isValid )
180 continue;
181
182 minimum += cell.metrics.minimum();
183 preferred += cell.metrics.preferred();
184
185 if ( maximum < maxMaximum )
186 {
187 if ( cell.stretch == 0 && !cell.canGrow )
188 {
189 maximum += cell.metrics.preferred();
190 }
191 else
192 {
193 if ( cell.metrics.maximum() == maxMaximum )
194 maximum = maxMaximum;
195 else
196 maximum += cell.metrics.maximum();
197 }
198 }
199
200 m_sumStretches += cell.stretch;
201 m_validCells++;
202 }
203
204 const qreal spacing = ( m_validCells - 1 ) * m_spacing;
205
206 minimum += spacing;
207 preferred += spacing;
208
209 if ( maximum < maxMaximum )
210 maximum += spacing;
211 }
212
213 m_boundingMetrics.setMinimum( minimum );
214 m_boundingMetrics.setPreferred( preferred );
215 m_boundingMetrics.setMaximum( maximum );
216}
217
218bool QskLayoutChain::setSpacing( qreal spacing )
219{
220 if ( m_spacing != spacing )
221 {
222 m_spacing = spacing;
223 return true;
224 }
225
226 return false;
227}
228
229QskLayoutChain::Segments QskLayoutChain::segments( qreal size ) const
230{
231 if ( m_validCells == 0 )
232 return Segments();
233
234 Segments segments;
235
236 if ( size <= m_boundingMetrics.minimum() )
237 {
238 segments = distributed( Qt::MinimumSize, 0.0, 0.0 );
239 }
240 else if ( size < m_boundingMetrics.preferred() )
241 {
242 segments = minimumExpanded( size );
243 }
244 else if ( size <= m_boundingMetrics.maximum() )
245 {
246 segments = preferredStretched( size );
247 }
248 else
249 {
250 const qreal padding = size - m_boundingMetrics.maximum();
251
252 qreal offset = 0.0;
253 qreal extra = 0.0;
254
255 switch( m_fillMode )
256 {
257 case Leading:
258 offset = padding;
259 break;
260
261 case Trailing:
262 break;
263
264 case Leading | Trailing:
265 offset = 0.5 * padding;
266 break;
267
268 default:
269 extra = padding / m_validCells;
270 }
271
272 segments = distributed( Qt::MaximumSize, offset, extra );
273 }
274
275 return segments;
276}
277
278QskLayoutChain::Segments QskLayoutChain::distributed(
279 int which, qreal offset, const qreal extra ) const
280{
281 qreal fillSpacing = 0.0;
282
283 Segments segments( m_cells.size() );
284
285 for ( int i = 0; i < segments.count(); i++ )
286 {
287 const auto& cell = m_cells[i];
288 auto& segment = segments[i];
289
290 if ( !cell.isValid )
291 {
292 segment.start = offset;
293 segment.length = 0.0;
294 }
295 else
296 {
297 offset += fillSpacing;
298 fillSpacing = m_spacing;
299
300 segment.start = offset;
301
302 qreal size = cell.metrics.metric( which );
303
304#if 1
305 if ( which == Qt::MaximumSize && size == QskLayoutMetrics::unlimited )
306 {
307 /*
308 We have some special handling in QskLayoutChain::finish,
309 that adds the preferred instead of the maximum
310 size of a cell to the bounding maximum size.
311 No good way to have this here, TODO ...
312 */
313 size = cell.metrics.metric( Qt::PreferredSize );
314 }
315#endif
316
317 if ( which == Qt::MaximumSize && cell.isShrunk )
318 {
319 segment.length = size;
320 offset += segment.length + extra;
321 }
322 else
323 {
324 segment.length = size + extra;
325 offset += segment.length;
326 }
327 }
328 }
329
330 return segments;
331}
332
333QskLayoutChain::Segments QskLayoutChain::minimumExpanded( qreal size ) const
334{
335 Segments segments( m_cells.size() );
336
337 qreal fillSpacing = 0.0;
338 qreal offset = 0.0;
339
340 /*
341 We have different options how to distribute the available space
342
343 - according to the preferred sizes
344
345 - items with a larger preferred size are stretchier: this is
346 what QGridLayoutEngine does
347
348 - somehow using the stretch factors
349 */
350
351 const qreal factor = ( size - m_boundingMetrics.minimum() ) /
352 ( m_boundingMetrics.preferred() - m_boundingMetrics.minimum() );
353
354 for ( int i = 0; i < m_cells.count(); i++ )
355 {
356 const auto& cell = m_cells[i];
357 auto& segment = segments[i];
358
359 if ( !cell.isValid )
360 {
361 segment.start = offset;
362 segment.length = 0.0;
363 }
364 else
365 {
366 offset += fillSpacing;
367 fillSpacing = m_spacing;
368
369 segment.start = offset;
370 segment.length = cell.metrics.minimum()
371 + factor * ( cell.metrics.preferred() - cell.metrics.minimum() );
372
373 offset += segment.length;
374 }
375 }
376
377 return segments;
378}
379
380QskLayoutChain::Segments QskLayoutChain::preferredStretched( qreal size ) const
381{
382 const int count = m_cells.size();
383
384 if ( count == 0 )
385 return Segments();
386
387 qreal sumFactors = 0.0;
388
389 QVarLengthArray< qreal > factors( count );
390 Segments segments( count );
391
392 for ( int i = 0; i < count; i++ )
393 {
394 const auto& cell = m_cells[i];
395
396 if ( !cell.isValid )
397 {
398 segments[i].length = 0.0;
399 factors[i] = -1.0;
400 continue;
401 }
402
403 if ( cell.metrics.preferred() >= cell.metrics.maximum() )
404 {
405 factors[i] = 0.0;
406 }
407 else
408 {
409 if ( m_sumStretches == 0 )
410 factors[i] = cell.canGrow ? 1.0 : 0.0;
411 else
412 factors[i] = cell.stretch;
413
414 }
415
416 sumFactors += factors[i];
417 }
418
419 qreal sumSizes = 0.0;
420
421 if ( sumFactors > 0.0 )
422 {
423 sumSizes = size - ( m_validCells - 1 ) * m_spacing;
424
425 Q_FOREVER
426 {
427 bool done = true;
428
429 for ( int i = 0; i < count; i++ )
430 {
431 if ( factors[i] < 0.0 )
432 continue;
433
434 const auto sz = sumSizes * factors[i] / sumFactors;
435
436 const auto& hint = m_cells[i].metrics;
437 const auto boundedSize =
438 qBound( hint.preferred(), sz, hint.maximum() );
439
440 if ( boundedSize != sz )
441 {
442 segments[i].length = boundedSize;
443 sumSizes -= boundedSize;
444 sumFactors -= factors[i];
445 factors[i] = -1.0;
446
447 done = false;
448 }
449 }
450
451 if ( done )
452 break;
453 }
454 }
455
456 qreal offset = 0;
457 qreal fillSpacing = 0.0;
458
459 for ( int i = 0; i < count; i++ )
460 {
461 const auto& cell = m_cells[i];
462 auto& segment = segments[i];
463
464 const auto& factor = factors[i];
465
466 if ( cell.isValid )
467 {
468 offset += fillSpacing;
469 fillSpacing = m_spacing;
470 }
471
472 segment.start = offset;
473
474 if ( factor >= 0.0 )
475 {
476 if ( factor > 0.0 )
477 segment.length = sumSizes * factor / sumFactors;
478 else
479 segment.length = cell.metrics.preferred();
480 }
481
482 offset += segment.length;
483 }
484
485 return segments;
486}
487
488#ifndef QT_NO_DEBUG_STREAM
489
490#include <qdebug.h>
491
492QDebug operator<<( QDebug debug, const QskLayoutChain::Segment& segment )
493{
494 QDebugStateSaver saver( debug );
495 debug.nospace();
496
497 debug << "( " << segment.start << ", " << segment.end() << " )";
498
499 return debug;
500}
501
502QDebug operator<<( QDebug debug, const QskLayoutChain::CellData& cell )
503{
504 QDebugStateSaver saver( debug );
505 debug.nospace();
506
507 if ( !cell.isValid )
508 {
509 debug << "( " << "Invalid " << " )";
510 }
511 else
512 {
513 debug << "( " << cell.metrics << ", "
514 << cell.stretch << ", " << cell.canGrow << " )";
515 }
516
517 return debug;
518}
519
520#endif
521