QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskLinearLayoutEngine.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskLinearLayoutEngine.h"
7#include "QskLayoutMetrics.h"
8#include "QskLayoutChain.h"
9#include "QskLayoutElement.h"
10#include "QskSizePolicy.h"
11#include "QskQuick.h"
12
13#include <qvector.h>
14
15namespace
16{
17 inline QskLayoutMetrics qskItemMetrics(
18 QQuickItem* item, Qt::Orientation orientation, qreal constraint )
19 {
20 const QskItemLayoutElement layoutItem( item );
21 return layoutItem.metrics( orientation, constraint );
22 }
23
24 class Element
25 {
26 public:
27 Element( QQuickItem* item );
28 Element( qreal spacing );
29 Element( const Element& );
30
31 Element& operator=( const Element& );
32
33 qreal spacing() const;
34 QQuickItem* item() const;
35
36 int stretch() const;
37 void setStretch( int );
38
39 bool isIgnored() const;
40
42 Qt::Orientation, bool isLayoutOrientation ) const;
43
44 private:
45
46 union
47 {
48 QQuickItem* m_item;
49 qreal m_spacing;
50 };
51
52 int m_stretch = -1;
53 bool m_isSpacer;
54 };
55
56 class ElementsVector : public std::vector< Element >
57 {
58 public:
59 // to avoid warnings when assigning size_t to int
60 inline int count() const { return static_cast< int >( size() ); }
61 };
62}
63
64Element::Element( QQuickItem* item )
65 : m_item( item )
66 , m_isSpacer( false )
67{
68}
69
70Element::Element( qreal spacing )
71 : m_spacing( spacing )
72 , m_isSpacer( true )
73{
74}
75
76Element::Element( const Element& other )
77 : m_stretch( other.m_stretch )
78 , m_isSpacer( other.m_isSpacer )
79{
80 if ( other.m_isSpacer )
81 m_spacing = other.m_spacing;
82 else
83 m_item = other.m_item;
84}
85
86Element& Element::operator=( const Element& other )
87{
88 m_isSpacer = other.m_isSpacer;
89
90 if ( other.m_isSpacer )
91 m_spacing = other.m_spacing;
92 else
93 m_item = other.m_item;
94
95 m_stretch = other.m_stretch;
96
97 return *this;
98}
99
100inline qreal Element::spacing() const
101{
102 return m_isSpacer ? m_spacing : -1.0;
103}
104
105inline QQuickItem* Element::item() const
106{
107 return m_isSpacer ? nullptr : m_item;
108}
109
110inline int Element::stretch() const
111{
112 return m_stretch;
113}
114
115inline void Element::setStretch( int stretch )
116{
117 m_stretch = stretch;
118}
119
120bool Element::isIgnored() const
121{
122 return !( m_isSpacer || qskIsVisibleToLayout( m_item ) );
123}
124
125QskLayoutChain::CellData Element::cell(
126 Qt::Orientation orientation, bool isLayoutOrientation ) const
127{
129 cell.canGrow = true;
130 cell.isValid = true;
131
132 if ( !m_isSpacer )
133 {
134 const auto policy = qskSizePolicy( m_item ).policy( orientation );
135
136 if ( isLayoutOrientation )
137 {
138 if ( m_stretch < 0 )
139 cell.stretch = ( policy & QskSizePolicy::ExpandFlag ) ? 1 : 0;
140 else
141 cell.stretch = m_stretch;
142 }
143
144 cell.canGrow = policy & QskSizePolicy::GrowFlag;
145 }
146 else
147 {
148 if ( isLayoutOrientation )
149 {
150 cell.metrics.setMinimum( m_spacing );
151 cell.metrics.setPreferred( m_spacing );
152
153 if ( m_stretch <= 0 )
154 cell.metrics.setMaximum( m_spacing );
155
156 cell.stretch = qMax( m_stretch, 0 );
157 }
158 }
159
160 return cell;
161}
162
163class QskLinearLayoutEngine::PrivateData
164{
165 public:
166
167 PrivateData( Qt::Orientation orientation, uint dimension )
168 : dimension( dimension )
169 , sumIgnored( -1 )
170 , orientation( orientation )
171 {
172 }
173
174 inline Element* elementAt( int index ) const
175 {
176 if ( ( index < 0 ) || ( index >= this->elements.count() ) )
177 return nullptr;
178
179 return const_cast< Element* >( &this->elements[index] );
180 }
181
182 ElementsVector elements;
183
184 uint dimension;
185
186 mutable int sumIgnored : 30;
187 unsigned int orientation : 2;
188};
189
190QskLinearLayoutEngine::QskLinearLayoutEngine(
191 Qt::Orientation orientation, uint dimension )
192 : m_data( new PrivateData( orientation, dimension ) )
193{
194}
195
196QskLinearLayoutEngine::~QskLinearLayoutEngine()
197{
198}
199
200bool QskLinearLayoutEngine::setOrientation( Qt::Orientation orientation )
201{
202 if ( m_data->orientation != orientation )
203 {
204 m_data->orientation = orientation;
205 invalidate( LayoutCache );
206
207 return true;
208 }
209
210 return false;
211}
212
213Qt::Orientation QskLinearLayoutEngine::orientation() const
214{
215 return static_cast< Qt::Orientation >( m_data->orientation );
216}
217
218bool QskLinearLayoutEngine::setDimension( uint dimension )
219{
220 if ( dimension < 1 )
221 dimension = 1;
222
223 if ( m_data->dimension != dimension )
224 {
225 m_data->dimension = dimension;
226 invalidate( LayoutCache );
227
228 return true;
229 }
230
231 return false;
232}
233
234uint QskLinearLayoutEngine::dimension() const
235{
236 return m_data->dimension;
237}
238
239int QskLinearLayoutEngine::count() const
240{
241 return m_data->elements.count();
242}
243
244bool QskLinearLayoutEngine::setStretchFactorAt( int index, int stretchFactor )
245{
246 if ( auto element = m_data->elementAt( index ) )
247 {
248 if ( stretchFactor < 0 )
249 stretchFactor = -1;
250
251 if ( element->stretch() != stretchFactor )
252 {
253 element->setStretch( stretchFactor );
254 invalidate( LayoutCache );
255
256 return true;
257 }
258 }
259
260 return false;
261}
262
263int QskLinearLayoutEngine::stretchFactorAt( int index ) const
264{
265 if ( const auto element = m_data->elementAt( index ) )
266 return element->stretch();
267
268 return -1;
269}
270
271int QskLinearLayoutEngine::insertItem( QQuickItem* item, int index )
272{
273 auto& elements = m_data->elements;
274
275 if ( index < 0 || index > count() )
276 {
277 index = elements.count();
278 elements.emplace_back( item );
279 }
280 else
281 {
282 elements.emplace( elements.begin() + index, item );
283 }
284
285 invalidate();
286 return index;
287}
288
289int QskLinearLayoutEngine::insertSpacerAt( int index, qreal spacing )
290{
291 spacing = qMax( spacing, static_cast< qreal >( 0.0 ) );
292
293 auto& elements = m_data->elements;
294
295 if ( index < 0 || index > count() )
296 {
297 index = elements.count();
298 elements.emplace_back( spacing );
299 }
300 else
301 {
302 elements.emplace( elements.begin() + index, spacing );
303 }
304
305 invalidate( LayoutCache );
306 return index;
307}
308
309bool QskLinearLayoutEngine::removeAt( int index )
310{
311 auto element = m_data->elementAt( index );
312 if ( element == nullptr )
313 return false;
314
315 if ( element->isIgnored() )
316 m_data->sumIgnored--;
317
318 const auto itemType = qskSizePolicy( element->item() ).constraintType();
319
320 int invalidationMode = LayoutCache;
321
322 if ( itemType > QskSizePolicy::Unconstrained )
323 invalidationMode |= ElementCache;
324
325 m_data->elements.erase( m_data->elements.begin() + index );
326 invalidate( invalidationMode );
327
328 return true;
329}
330
331bool QskLinearLayoutEngine::clear()
332{
333 if ( count() <= 0 )
334 return false;
335
336 m_data->elements.clear();
337 invalidate();
338
339 return true;
340}
341
342QskSizePolicy QskLinearLayoutEngine::sizePolicyAt( int index ) const
343{
344 return qskSizePolicy( itemAt( index ) );
345}
346
347int QskLinearLayoutEngine::indexOf( const QQuickItem* item ) const
348{
349 if ( item )
350 {
351 /*
352 indexOf is often called after inserting an item to
353 set additinal properties. So we search in reverse order
354 */
355
356 for ( int i = count() - 1; i >= 0; --i )
357 {
358 if ( itemAt( i ) == item )
359 return i;
360 }
361 }
362
363 return -1;
364}
365
366QQuickItem* QskLinearLayoutEngine::itemAt( int index ) const
367{
368 if ( const auto element = m_data->elementAt( index ) )
369 return element->item();
370
371 return nullptr;
372}
373
374qreal QskLinearLayoutEngine::spacerAt( int index ) const
375{
376 if ( const auto element = m_data->elementAt( index ) )
377 return element->spacing();
378
379 return -1.0;
380}
381
382void QskLinearLayoutEngine::layoutItems()
383{
384 uint row = 0;
385 uint col = 0;
386
387 for ( const auto& element : m_data->elements )
388 {
389 if ( element.isIgnored() )
390 continue;
391
392 if ( auto item = element.item() )
393 {
394 if ( qskIsAdjustableByLayout( item ) )
395 {
396 const QRect grid( col, row, 1, 1 );
397
398 const QskItemLayoutElement layoutElement( item );
399
400 const auto rect = geometryAt( &layoutElement, grid );
401 if ( rect.size().isValid() )
402 qskSetItemGeometry( item, rect );
403 }
404 }
405
406 if ( m_data->orientation == Qt::Horizontal )
407 {
408 if ( ++col == m_data->dimension )
409 {
410 col = 0;
411 row++;
412 }
413 }
414 else
415 {
416 if ( ++row == m_data->dimension )
417 {
418 row = 0;
419 col++;
420 }
421 }
422 }
423}
424
425int QskLinearLayoutEngine::effectiveCount( Qt::Orientation orientation ) const
426{
427 const uint cellCount = effectiveCount();
428
429 if ( orientation == m_data->orientation )
430 {
431 return qMin( cellCount, m_data->dimension );
432 }
433 else
434 {
435 int count = cellCount / m_data->dimension;
436 if ( cellCount % m_data->dimension )
437 count++;
438
439 return count;
440 }
441}
442
443int QskLinearLayoutEngine::effectiveCount() const
444{
445 if ( m_data->sumIgnored < 0 )
446 {
447 m_data->sumIgnored = 0;
448
449 for ( const auto& element : m_data->elements )
450 {
451 if ( element.isIgnored() )
452 m_data->sumIgnored++;
453 }
454 }
455
456 return m_data->elements.count() - m_data->sumIgnored;
457}
458
459void QskLinearLayoutEngine::invalidateElementCache()
460{
461 m_data->sumIgnored = -1;
462}
463
464void QskLinearLayoutEngine::setupChain( Qt::Orientation orientation,
465 const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const
466{
467 uint index1 = 0;
468 uint index2 = 0;
469
470 const bool isLayoutOrientation = ( orientation == m_data->orientation );
471
472 qreal constraint = -1.0;
473
474 for ( const auto& element : m_data->elements )
475 {
476 if ( element.isIgnored() )
477 continue;
478
479 if ( !constraints.isEmpty() )
480 constraint = constraints[index1].length;
481
482 auto cell = element.cell( orientation, isLayoutOrientation );
483
484 if ( element.item() )
485 cell.metrics = qskItemMetrics( element.item(), orientation, constraint );
486
487 chain.expandCell( index2, cell );
488
489 if ( isLayoutOrientation )
490 {
491 if ( ++index2 == m_data->dimension )
492 {
493 index2 = 0;
494 index1++;
495 }
496 }
497 else
498 {
499 if ( ++index1 == m_data->dimension )
500 {
501 index1 = 0;
502 index2++;
503 }
504 }
505 }
506}