QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGridLayoutEngine.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGridLayoutEngine.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
15#include <vector>
16#include <functional>
17
18static inline qreal qskSegmentLength(
19 const QskLayoutChain::Segments& s, int start, int end )
20{
21 return s[ end ].start - s[ start ].start + s[ end ].length;
22}
23
24namespace
25{
26 class Settings
27 {
28 public:
29 class Setting
30 {
31 public:
32 inline bool isDefault() const
33 {
34 return m_stretch < 0 && m_metrics.isDefault();
35 }
36
37 bool setStretch( int stretch )
38 {
39 if ( stretch != m_stretch )
40 {
41 m_stretch = stretch;
42 return true;
43 }
44 return false;
45 }
46
47 bool setMetric( Qt::SizeHint which, qreal metric )
48 {
49 if ( metric != m_metrics.metric( which ) )
50 {
51 m_metrics.setMetric( which, metric );
52 return true;
53 }
54 return false;
55 }
56
57 QskLayoutChain::CellData cell() const
58 {
60 cell.metrics = m_metrics.normalized();
61 cell.stretch = m_stretch;
62 cell.canGrow = m_stretch != 0;
63 cell.isValid = true;
64
65 return cell;
66 }
67
68 inline int stretch() const { return m_stretch; }
69 inline QskLayoutMetrics metrics() const { return m_metrics; }
70
71 int position = -1;
72
73 private:
74 int m_stretch = -1;
75 QskLayoutMetrics m_metrics;
76 };
77
78 int maxPosition() const
79 {
80 if ( m_settings.empty() )
81 return -1;
82
83 return m_settings.back().position;
84 }
85
86 void clear()
87 {
88 m_settings.clear();
89 }
90
91 bool setStretchAt( int index, int stretch )
92 {
93 auto setStretch = [stretch]( Setting& s )
94 { return s.setStretch( stretch ); };
95
96 return setValueAt( index, setStretch );
97 }
98
99 bool setMetricAt( int index, Qt::SizeHint which, qreal size )
100 {
101 auto setMetric = [which, size]( Setting& s )
102 { return s.setMetric( which, size ); };
103
104 return setValueAt( index, setMetric );
105 }
106
107 Setting settingAt( int index ) const
108 {
109 auto it = lowerBound( index );
110 if ( it != m_settings.end() )
111 return *it;
112
113 return Setting();
114 }
115
116 const std::vector< Setting >& settings() const { return m_settings; }
117
118 private:
119 inline bool setValueAt( int pos,
120 const std::function< bool( Setting& ) >& modify )
121 {
122 if ( pos < 0 )
123 return false;
124
125 bool isModified;
126
127 auto it = lowerBound( pos );
128
129 if ( it != m_settings.end() && it->position == pos )
130 {
131 isModified = modify( *it );
132
133 if ( isModified && it->isDefault() )
134 m_settings.erase( it );
135 }
136 else
137 {
138 Setting setting;
139 isModified = modify( setting );
140
141 if ( isModified )
142 {
143 setting.position = pos;
144 m_settings.insert( it, setting );
145 }
146 }
147
148 return isModified;
149 }
150
151 inline std::vector< Setting >::iterator lowerBound( int index ) const
152 {
153 auto cmp = []( const Setting& setting, const int& pos )
154 { return setting.position < pos; };
155
156 auto& settings = const_cast< std::vector< Setting >& >( m_settings );
157 return std::lower_bound( settings.begin(), settings.end(), index, cmp );
158 }
159
160 std::vector< Setting > m_settings; // a flat map
161 };
162}
163
164namespace
165{
166 inline QskLayoutMetrics qskItemMetrics(
167 QQuickItem* item, Qt::Orientation orientation, qreal constraint )
168 {
169 const QskItemLayoutElement layoutItem( item );
170 return layoutItem.metrics( orientation, constraint );
171 }
172
173 class Element
174 {
175 public:
176 Element( QQuickItem*, const QRect& );
177 Element( const QSizeF& spacing, const QRect& );
178 Element( const Element& );
179
180 Element& operator=( const Element& );
181
182 QSizeF spacing() const;
183 QQuickItem* item() const;
184
185 QRect grid() const;
186 void setGrid( const QRect& );
187
188 QRect minimumGrid() const;
189
190 bool isIgnored() const;
191 QskLayoutChain::CellData cell( Qt::Orientation ) const;
192
193 void transpose();
194
195 private:
196
197 union
198 {
199 QQuickItem* m_item;
200 QSizeF m_spacing;
201 };
202
203 QRect m_grid;
204 bool m_isSpacer;
205 };
206
207 class ElementsVector : public std::vector< Element >
208 {
209 public:
210 // to avoid warnings when assigning size_t to int
211 inline int count() const { return static_cast< int >( size() ); }
212 };
213}
214
215Element::Element( QQuickItem* item, const QRect& grid )
216 : m_item( item )
217 , m_grid( grid )
218 , m_isSpacer( false )
219{
220}
221
222Element::Element( const QSizeF& spacing, const QRect& grid )
223 : m_spacing( spacing )
224 , m_grid( grid )
225 , m_isSpacer( true )
226{
227}
228
229Element::Element( const Element& other )
230 : m_grid( other.m_grid )
231 , m_isSpacer (other.m_isSpacer )
232{
233 if ( other.m_isSpacer )
234 m_spacing = other.m_spacing;
235 else
236 m_item = other.m_item;
237}
238
239Element& Element::operator=( const Element& other )
240{
241 m_isSpacer = other.m_isSpacer;
242
243 if ( other.m_isSpacer )
244 m_spacing = other.m_spacing;
245 else
246 m_item = other.m_item;
247
248 m_grid = other.m_grid;
249
250 return *this;
251}
252
253inline QSizeF Element::spacing() const
254{
255 return m_isSpacer ? m_spacing : QSizeF();
256}
257
258inline QQuickItem* Element::item() const
259{
260 return m_isSpacer ? nullptr : m_item;
261}
262
263QRect Element::grid() const
264{
265 return m_grid;
266}
267
268void Element::setGrid( const QRect& grid )
269{
270 m_grid = grid;
271}
272
273QRect Element::minimumGrid() const
274{
275 return QRect( m_grid.left(), m_grid.top(),
276 qMax( m_grid.width(), 1 ), qMax( m_grid.height(), 1 ) );
277}
278
279bool Element::isIgnored() const
280{
281 return !( m_isSpacer || qskIsVisibleToLayout( m_item ) );
282}
283
284QskLayoutChain::CellData Element::cell( Qt::Orientation orientation ) const
285{
287 cell.isValid = true;
288
289 if ( m_isSpacer )
290 {
291 const qreal value = ( orientation == Qt::Horizontal )
292 ? m_spacing.width() : m_spacing.height();
293
294 cell.metrics.setMinimum( value );
295 cell.metrics.setPreferred( value );
296 cell.metrics.setMaximum( value );
297 }
298 else
299 {
300 const auto policy = qskSizePolicy( m_item ).policy( orientation );
301
302 cell.canGrow = policy & QskSizePolicy::GrowFlag;
303
304 if ( policy & QskSizePolicy::ExpandFlag )
305 cell.stretch = 1;
306 }
307
308 return cell;
309}
310
311void Element::transpose()
312{
313 m_grid.setRect( m_grid.top(), m_grid.left(),
314 m_grid.height(), m_grid.width() );
315}
316
317class QskGridLayoutEngine::PrivateData
318{
319 public:
320 inline Element* elementAt( int index ) const
321 {
322 if ( index < 0 || index >= this->elements.count() )
323 return nullptr;
324
325 return const_cast< Element* >( &this->elements[index] );
326 }
327
328 int insertElement( QQuickItem* item, QSizeF spacing, QRect grid )
329 {
330 // -1 means unlimited, while 0 does not make any sense
331 if ( grid.width() == 0 )
332 grid.setWidth( 1 );
333
334 if ( grid.height() == 0 )
335 grid.setHeight( 1 );
336
337 if ( item )
338 {
339 elements.push_back( Element( item, grid ) );
340 }
341 else
342 {
343 if ( spacing.width() < 0.0 )
344 spacing.setWidth( 0.0 );
345
346 if ( spacing.height() < 0.0 )
347 spacing.setHeight( 0.0 );
348
349 elements.push_back( Element( spacing, grid ) );
350 }
351
352 grid = effectiveGrid( elements.back() );
353
354 rowCount = qMax( rowCount, grid.bottom() + 1 );
355 columnCount = qMax( columnCount, grid.right() + 1 );
356
357 return this->elements.count() - 1;
358 }
359
360 QRect effectiveGrid( const Element& element ) const
361 {
362 QRect r = element.grid();
363
364 if ( r.width() <= 0 )
365 r.setRight( qMax( this->columnCount - 1, r.left() ) );
366
367 if ( r.height() <= 0 )
368 r.setBottom( qMax( this->rowCount - 1, r.top() ) );
369
370 return r;
371 }
372
373 Settings& settings( Qt::Orientation orientation ) const
374 {
375 auto that = const_cast< PrivateData* >( this );
376 return ( orientation == Qt::Horizontal )
377 ? that->columnSettings : that->rowSettings;
378 }
379
380 ElementsVector elements;
381
382 Settings rowSettings;
383 Settings columnSettings;
384
385 int rowCount = 0;
386 int columnCount = 0;
387};
388
389QskGridLayoutEngine::QskGridLayoutEngine()
390 : m_data( new PrivateData() )
391{
392}
393
394QskGridLayoutEngine::~QskGridLayoutEngine()
395{
396}
397
398int QskGridLayoutEngine::count() const
399{
400 return m_data->elements.count();
401}
402
403bool QskGridLayoutEngine::setStretchFactor(
404 int pos, int stretch, Qt::Orientation orientation )
405{
406 if ( pos < 0 )
407 return false;
408
409 if ( stretch < 0 )
410 stretch = -1;
411
412 if ( !m_data->settings( orientation ).setStretchAt( pos, stretch ) )
413 return false;
414
415 if ( orientation == Qt::Horizontal )
416 {
417 if ( pos >= m_data->columnCount )
418 m_data->columnCount = pos + 1;
419 }
420 else
421 {
422 if ( pos >= m_data->rowCount )
423 m_data->rowCount = pos + 1;
424 }
425
426 invalidate();
427 return true;
428}
429
430int QskGridLayoutEngine::stretchFactor(
431 int pos, Qt::Orientation orientation ) const
432{
433 const auto setting = m_data->settings( orientation ).settingAt( pos );
434 return ( setting.position == pos ) ? setting.stretch() : 0;
435}
436
437bool QskGridLayoutEngine::setRowSizeHint(
438 int row, Qt::SizeHint which, qreal height )
439{
440 if ( !m_data->rowSettings.setMetricAt( row, which, height ) )
441 return false;
442
443 if ( row >= m_data->rowCount )
444 m_data->rowCount = row + 1;
445
446 invalidate();
447 return true;
448}
449
450qreal QskGridLayoutEngine::rowSizeHint( int row, Qt::SizeHint which ) const
451{
452 const auto& settings = m_data->rowSettings;
453 return settings.settingAt( row ).metrics().metric( which );
454}
455
456bool QskGridLayoutEngine::setColumnSizeHint(
457 int column, Qt::SizeHint which, qreal width )
458{
459 if ( !m_data->columnSettings.setMetricAt( column, which, width ) )
460 return false;
461
462 if ( column >= m_data->columnCount )
463 m_data->columnCount = column + 1;
464
465 invalidate();
466 return true;
467}
468
469qreal QskGridLayoutEngine::columnSizeHint( int column, Qt::SizeHint which ) const
470{
471 const auto& settings = m_data->columnSettings;
472 return settings.settingAt( column ).metrics().metric( which );
473}
474
475int QskGridLayoutEngine::insertItem( QQuickItem* item, const QRect& grid )
476{
477 invalidate();
478 return m_data->insertElement( item, QSizeF(), grid );
479}
480
481int QskGridLayoutEngine::insertSpacer( const QSizeF& spacing, const QRect& grid )
482{
483 return m_data->insertElement( nullptr, spacing, grid );
484}
485
486bool QskGridLayoutEngine::removeAt( int index )
487{
488 const auto elementAt = m_data->elementAt( index );
489 if ( elementAt == nullptr )
490 return false;
491
492 const auto grid = elementAt->minimumGrid();
493
494 auto& elements = m_data->elements;
495 elements.erase( elements.begin() + index );
496
497 // doing a lazy recalculation instead ??
498
499 if ( grid.bottom() >= m_data->rowCount
500 || grid.right() >= m_data->columnCount )
501 {
502 int maxRow = m_data->rowSettings.maxPosition();
503 int maxColumn = m_data->columnSettings.maxPosition();
504
505 for ( const auto& element : elements )
506 {
507 const auto minGrid = element.minimumGrid();
508
509 maxRow = qMax( maxRow, minGrid.bottom() );
510 maxColumn = qMax( maxColumn, minGrid.right() );
511 }
512
513 m_data->rowCount = maxRow + 1;
514 m_data->columnCount = maxColumn + 1;
515 }
516
517 invalidate();
518 return true;
519}
520
521bool QskGridLayoutEngine::clear()
522{
523 m_data->elements.clear();
524 m_data->rowSettings.clear();
525 m_data->columnSettings.clear();
526
527 m_data->rowCount = m_data->columnCount = 0;
528
529 invalidate();
530 return true;
531}
532
533int QskGridLayoutEngine::indexAt( int row, int column ) const
534{
535 if ( row < m_data->rowCount && column < m_data->columnCount )
536 {
537 for ( uint i = 0; i < m_data->elements.size(); i++ )
538 {
539 const auto grid = m_data->effectiveGrid( m_data->elements[i] );
540 if ( grid.contains( column, row ) )
541 return i;
542 }
543 }
544
545 return -1;
546}
547
548QQuickItem* QskGridLayoutEngine::itemAt( int index ) const
549{
550 if ( const auto element = m_data->elementAt( index ) )
551 return element->item();
552
553 return nullptr;
554}
555
556QskSizePolicy QskGridLayoutEngine::sizePolicyAt( int index ) const
557{
558 return qskSizePolicy( itemAt( index ) );
559}
560
561int QskGridLayoutEngine::indexOf( const QQuickItem* item ) const
562{
563 if ( item )
564 {
565 /*
566 indexOf is often called after inserting an item to
567 set additinal properties. So we search in reverse order
568 */
569
570 for ( int i = count() - 1; i >= 0; --i )
571 {
572 if ( itemAt( i ) == item )
573 return i;
574 }
575 }
576
577 return -1;
578}
579
580QSizeF QskGridLayoutEngine::spacerAt( int index ) const
581{
582 if ( const auto element = m_data->elementAt( index ) )
583 return element->spacing();
584
585 return QSizeF();
586}
587
588QQuickItem* QskGridLayoutEngine::itemAt( int row, int column ) const
589{
590 return itemAt( indexAt( row, column ) );
591}
592
593bool QskGridLayoutEngine::setGridAt( int index, const QRect& grid )
594{
595 if ( auto element = m_data->elementAt( index ) )
596 {
597 if ( element->grid() != grid )
598 {
599 element->setGrid( grid );
600 invalidate();
601
602 return true;
603 }
604 }
605
606 return false;
607}
608
609QRect QskGridLayoutEngine::gridAt( int index ) const
610{
611 if ( auto element = m_data->elementAt( index ) )
612 return element->grid();
613
614 return QRect();
615}
616
617QRect QskGridLayoutEngine::effectiveGridAt( int index ) const
618{
619 if ( auto element = m_data->elementAt( index ) )
620 return m_data->effectiveGrid( *element );
621
622 return QRect();
623}
624
625void QskGridLayoutEngine::invalidateElementCache()
626{
627}
628
629void QskGridLayoutEngine::layoutItems()
630{
631 for ( const auto& element : m_data->elements )
632 {
633 auto item = element.item();
634
635 if ( qskIsAdjustableByLayout( item ) )
636 {
637 const auto grid = m_data->effectiveGrid( element );
638
639 const QskItemLayoutElement layoutElement( item );
640
641 const auto rect = geometryAt( &layoutElement, grid );
642 if ( rect.size().isValid() )
643 qskSetItemGeometry( item, rect );
644 }
645 }
646}
647
648void QskGridLayoutEngine::transpose()
649{
650 for ( auto& element : m_data->elements )
651 element.transpose();
652
653 qSwap( m_data->columnSettings, m_data->rowSettings );
654 qSwap( m_data->columnCount, m_data->rowCount );
655
656 invalidate();
657}
658
659int QskGridLayoutEngine::effectiveCount(
660 Qt::Orientation orientation ) const
661{
662 return ( orientation == Qt::Horizontal )
663 ? m_data->columnCount : m_data->rowCount;
664}
665
666void QskGridLayoutEngine::setupChain( Qt::Orientation orientation,
667 const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const
668{
669 /*
670 We collect all information from the simple elements first
671 before adding those that occupy more than one cell
672 */
673 QVarLengthArray< const Element* > postponed;
674 postponed.reserve( m_data->elements.count() );
675
676 for ( const auto& element : m_data->elements )
677 {
678 if ( element.isIgnored() )
679 continue;
680
681 auto grid = m_data->effectiveGrid( element );
682 if ( orientation == Qt::Horizontal )
683 grid.setRect( grid.y(), grid.x(), grid.height(), grid.width() );
684
685 if ( grid.height() == 1 )
686 {
687 qreal constraint = -1.0;
688 if ( !constraints.isEmpty() )
689 constraint = qskSegmentLength( constraints, grid.left(), grid.right() );
690
691 auto cell = element.cell( orientation );
692
693 if ( element.item() )
694 cell.metrics = qskItemMetrics( element.item(), orientation, constraint );
695
696 chain.expandCell( grid.top(), cell );
697 }
698 else
699 {
700 postponed += &element;
701 }
702 }
703
704 const auto& settings = m_data->settings( orientation );
705
706 for ( const auto& setting : settings.settings() )
707 chain.shrinkCell( setting.position, setting.cell() );
708
709 for ( const auto element : postponed )
710 {
711 auto grid = m_data->effectiveGrid( *element );
712 if ( orientation == Qt::Horizontal )
713 grid.setRect( grid.y(), grid.x(), grid.height(), grid.width() );
714
715 qreal constraint = -1.0;
716 if ( !constraints.isEmpty() )
717 constraint = qskSegmentLength( constraints, grid.left(), grid.right() );
718
719 auto cell = element->cell( orientation );
720 cell.metrics = qskItemMetrics( element->item(), orientation, constraint );
721
722 chain.expandCells( grid.top(), grid.height(), cell );
723 }
724}