From f5ee8a38535113cb85b7c37edf7373161f6d3d1c Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sat, 27 Jul 2019 12:36:52 +0200 Subject: [PATCH] QGridLayoutEngine replaced by QskGridLayoutEngine also for QskGridBox --- src/layouts/QskGridBox.cpp | 282 +++---- src/layouts/QskGridBox.h | 45 +- src/layouts/QskGridLayoutEngine.cpp | 1065 +++++++++++++----------- src/layouts/QskGridLayoutEngine.h | 99 +-- src/layouts/QskLayoutChain.cpp | 233 ++++-- src/layouts/QskLayoutChain.h | 72 +- src/layouts/QskLayoutEngine2D.cpp | 455 +++++++++++ src/layouts/QskLayoutEngine2D.h | 104 +++ src/layouts/QskLayoutHint.cpp | 9 - src/layouts/QskLayoutHint.h | 11 +- src/layouts/QskLinearBox.cpp | 57 +- src/layouts/QskLinearBox.h | 12 +- src/layouts/QskLinearLayoutEngine.cpp | 1067 +++++++------------------ src/layouts/QskLinearLayoutEngine.h | 79 +- src/src.pro | 10 +- 15 files changed, 1922 insertions(+), 1678 deletions(-) create mode 100644 src/layouts/QskLayoutEngine2D.cpp create mode 100644 src/layouts/QskLayoutEngine2D.h diff --git a/src/layouts/QskGridBox.cpp b/src/layouts/QskGridBox.cpp index ed514ac4..8668d4cc 100644 --- a/src/layouts/QskGridBox.cpp +++ b/src/layouts/QskGridBox.cpp @@ -7,6 +7,7 @@ #include "QskGridLayoutEngine.h" #include "QskLayoutConstraint.h" #include "QskEvent.h" +#include static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on ) { @@ -44,10 +45,44 @@ static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on } } +static void qskUpdateFocusChain( + QskGridBox* box, const QskGridLayoutEngine* engine, + QQuickItem* item, const QRect& grid ) +{ + auto comparePosition = + [item, engine]( const QPoint& pos, const QQuickItem* child ) + { + if ( item != child ) + { + const int index = engine->indexOf( child ); + if ( index >= 0 ) + { + const auto grid = engine->gridAt( index ); + if ( pos.y() < grid.y() ) + return true; + + if ( pos.y() == grid.y() && pos.x() < grid.x() ) + return true; + } + } + + return false; + }; + + const auto children = box->childItems(); + + auto it = std::upper_bound( children.begin(), children.end(), + grid.topLeft(), comparePosition ); + + if ( it != children.end() ) + item->stackBefore( *it ); +} + class QskGridBox::PrivateData { public: QskGridLayoutEngine engine; + bool blockAutoRemove = false; }; QskGridBox::QskGridBox( QQuickItem* parent ) @@ -60,30 +95,67 @@ QskGridBox::~QskGridBox() { } -void QskGridBox::addItem( QQuickItem* item, +int QskGridBox::addItem( QQuickItem* item, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment ) { - if ( item == nullptr ) - return; + if ( item == nullptr || row < 0 || column < 0 ) + return -1; - if ( item->parent() == nullptr ) - item->setParent( this ); + rowSpan = qMax( rowSpan, -1 ); + columnSpan = qMax( columnSpan, -1 ); - if ( item->parentItem() != this ) - item->setParentItem( this ); + auto& engine = m_data->engine; - qskSetItemActive( this, item, true ); + const QRect itemGrid( column, row, columnSpan, rowSpan ); + int index = -1; - // What about the focus tab chain - TODO ... ???? - // check if item is already inserted ??? + if ( item->parentItem() == this ) + { + index = indexOf( item ); + if ( index >= 0 ) + { + if ( engine.gridAt( index ) == itemGrid ) + { + if ( engine.setAlignmentAt( index, alignment ) ) + polish(); - m_data->engine.insertItem( - item, row, column, rowSpan, columnSpan, alignment ); + return index; + } + } + } + + if ( index < 0 ) + { + if ( item->parent() == nullptr ) + item->setParent( this ); + + if ( item->parentItem() != this ) + item->setParentItem( this ); + + qskSetItemActive( this, item, true ); + index = engine.insertItem( item, itemGrid, alignment ); + } + + if ( engine.count() > 1 ) + qskUpdateFocusChain( this, &engine, item, itemGrid ); resetImplicitSize(); polish(); + return index; +} + +int QskGridBox::addSpacer( qreal spacing, + int row, int column, int rowSpan, int columnSpan ) +{ + const int index = m_data->engine.insertSpacer( + spacing, QRect( column, row, columnSpan, rowSpan ) ); + + resetImplicitSize(); + polish(); + + return index; } void QskGridBox::removeAt( int index ) @@ -106,25 +178,29 @@ void QskGridBox::removeItem( const QQuickItem* item ) void QskGridBox::clear( bool autoDelete ) { - for ( int i = count() - 1; i >= 0; i-- ) + m_data->blockAutoRemove = true; + + for ( int i = 0; i < count(); i++ ) { - auto item = itemAtIndex( i ); - - removeAt( i ); - - if( item ) + if ( auto item = itemAtIndex( i ) ) { + qskSetItemActive( this, item, false ); + if( autoDelete && ( item->parent() == this ) ) delete item; else item->setParentItem( nullptr ); } } + + m_data->blockAutoRemove = false; + + m_data->engine.clear(); } int QskGridBox::count() const { - return m_data->engine.itemCount(); + return m_data->engine.count(); } int QskGridBox::rowCount() const @@ -157,47 +233,30 @@ int QskGridBox::indexAt( int row, int column ) const return m_data->engine.indexAt( row, column ); } -int QskGridBox::rowOfIndex( int index ) const +QRect QskGridBox::gridOfIndex( int index ) const { - return m_data->engine.rowOfIndex( index ); + return m_data->engine.gridAt( index ); } -int QskGridBox::rowSpanOfIndex( int index ) const +QRect QskGridBox::effectiveGridOfIndex( int index ) const { - return m_data->engine.rowSpanOfIndex( index ); + return m_data->engine.effectiveGridAt( index ); } -int QskGridBox::columnOfIndex( int index ) const +void QskGridBox::setDefaultAlignment( Qt::Alignment alignment ) { - return m_data->engine.columnOfIndex( index ); + if ( m_data->engine.setDefaultAlignment( alignment ) ) + Q_EMIT defaultAlignmentChanged(); } -int QskGridBox::columnSpanOfIndex( int index ) const +Qt::Alignment QskGridBox::defaultAlignment() const { - return m_data->engine.columnSpanOfIndex( index ); + return m_data->engine.defaultAlignment(); } void QskGridBox::setSpacing( Qt::Orientations orientations, qreal spacing ) { - spacing = qMax( spacing, 0.0 ); - - bool doUpdate = false; - - auto& engine = m_data->engine; - - for ( const auto o : { Qt::Horizontal, Qt::Vertical } ) - { - if ( orientations & o ) - { - if ( spacing != engine.spacing( o ) ) - { - engine.setSpacing( o, spacing ); - doUpdate = true; - } - } - } - - if ( doUpdate ) + if ( m_data->engine.setSpacing( spacing, orientations ) ) { resetImplicitSize(); polish(); @@ -209,7 +268,7 @@ void QskGridBox::resetSpacing( Qt::Orientations orientations ) for ( const auto o : { Qt::Horizontal, Qt::Vertical } ) { if ( orientations & o ) - setSpacing( o, QskGridLayoutEngine::defaultSpacing( o ) ); + setSpacing( o, m_data->engine.defaultSpacing( o ) ); } } @@ -218,72 +277,26 @@ qreal QskGridBox::spacing( Qt::Orientation orientation ) const return m_data->engine.spacing( orientation ); } -void QskGridBox::setRowSpacing( int row, qreal spacing ) -{ - spacing = qMax( spacing, 0.0 ); - - auto& engine = m_data->engine; - - if ( spacing != engine.spacingAt( Qt::Vertical, row ) ) - { - engine.setSpacingAt( Qt::Vertical, row, spacing ); - polish(); - } -} - -qreal QskGridBox::rowSpacing( int row ) const -{ - return m_data->engine.spacingAt( Qt::Vertical, row ); -} - -void QskGridBox::setColumnSpacing( int column, qreal spacing ) -{ - spacing = qMax( spacing, 0.0 ); - - auto& engine = m_data->engine; - - if ( spacing != engine.spacingAt( Qt::Horizontal, column ) ) - { - engine.setSpacingAt( Qt::Horizontal, column, spacing ); - polish(); - } -} - -qreal QskGridBox::columnSpacing( int column ) const -{ - return m_data->engine.spacingAt( Qt::Horizontal, column ); -} - void QskGridBox::setRowStretchFactor( int row, int stretch ) { - auto& engine = m_data->engine; - - if ( stretch != engine.stretchFactorAt( Qt::Vertical, row ) ) - { - engine.setStretchFactorAt( Qt::Vertical, row, stretch ); + if ( m_data->engine.setStretchFactor( row, stretch, Qt::Vertical ) ) polish(); - } } int QskGridBox::rowStretchFactor( int row ) const { - return m_data->engine.stretchFactorAt( Qt::Vertical, row ); + return m_data->engine.stretchFactor( row, Qt::Vertical ); } void QskGridBox::setColumnStretchFactor( int column, int stretch ) { - auto& engine = m_data->engine; - - if ( stretch != engine.stretchFactorAt( Qt::Horizontal, column ) ) - { - engine.setStretchFactorAt( Qt::Horizontal, column, stretch ); + if ( m_data->engine.setStretchFactor( column, stretch, Qt::Horizontal ) ) polish(); - } } int QskGridBox::columnStretchFactor( int column ) const { - return m_data->engine.stretchFactorAt( Qt::Horizontal, column ); + return m_data->engine.stretchFactor( column, Qt::Horizontal ); } void QskGridBox::setRowFixedHeight( int row, qreal height ) @@ -298,79 +311,46 @@ void QskGridBox::setColumnFixedWidth( int column, qreal width ) setColumnSizeHint( column, Qt::MaximumSize, width ); } -void QskGridBox::setRowAlignment( int row, Qt::Alignment alignment ) -{ - auto& engine = m_data->engine; - - if ( engine.alignmentAt( Qt::Vertical, row ) != alignment ) - { - engine.setAlignmentAt( Qt::Vertical, row, alignment ); - polish(); - } -} - -Qt::Alignment QskGridBox::rowAlignment( int row ) const -{ - return m_data->engine.alignmentAt( Qt::Vertical, row ); -} - -void QskGridBox::setColumnAlignment( int column, Qt::Alignment alignment ) -{ - auto& engine = m_data->engine; - - if ( engine.alignmentAt( Qt::Horizontal, column ) != alignment ) - { - engine.setAlignmentAt( Qt::Horizontal, column, alignment ); - polish(); - } -} - -Qt::Alignment QskGridBox::columnAlignment( int column ) const -{ - return m_data->engine.alignmentAt( Qt::Horizontal, column ); -} - void QskGridBox::setAlignment( const QQuickItem* item, Qt::Alignment alignment ) { auto& engine = m_data->engine; - if ( engine.alignmentOf( item ) != alignment ) + const int index = engine.indexOf( item ); + if ( index >= 0 ) { - engine.setAlignmentOf( item, alignment ); - polish(); + if ( engine.setAlignmentAt( index, alignment ) ) + polish(); } } Qt::Alignment QskGridBox::alignment( const QQuickItem* item ) const { - return m_data->engine.alignmentOf( item ); + const auto& engine = m_data->engine; + return engine.alignmentAt( engine.indexOf( item ) ); } void QskGridBox::setRetainSizeWhenHidden( const QQuickItem* item, bool on ) { auto& engine = m_data->engine; - if ( engine.retainSizeWhenHiddenOf( item ) != on ) + const int index = engine.indexOf( item ); + if ( index >= 0 ) { - engine.setRetainSizeWhenHiddenOf( item, on ); - invalidate(); + if ( engine.setRetainSizeWhenHiddenAt( index, on ) ) + invalidate(); } } bool QskGridBox::retainSizeWhenHidden( const QQuickItem* item ) const { - return m_data->engine.retainSizeWhenHiddenOf( item ); + const auto& engine = m_data->engine; + return engine.retainSizeWhenHiddenAt( engine.indexOf( item ) ); } void QskGridBox::setRowSizeHint( int row, Qt::SizeHint which, qreal height ) { - auto& engine = m_data->engine; - - if ( height != engine.rowSizeHint( row, which ) ) - { - engine.setRowSizeHint( row, which, height ); + if ( m_data->engine.setRowSizeHint( row, which, height ) ) polish(); - } } qreal QskGridBox::rowSizeHint( int row, Qt::SizeHint which ) const @@ -380,13 +360,8 @@ qreal QskGridBox::rowSizeHint( int row, Qt::SizeHint which ) const void QskGridBox::setColumnSizeHint( int column, Qt::SizeHint which, qreal width ) { - auto& engine = m_data->engine; - - if ( width != engine.columnSizeHint( column, which ) ) - { - engine.setColumnSizeHint( column, which, width ); + if ( m_data->engine.setColumnSizeHint( column, which, width ) ) polish(); - } } qreal QskGridBox::columnSizeHint( int column, Qt::SizeHint which ) const @@ -421,7 +396,8 @@ qreal QskGridBox::heightForWidth( qreal width ) const auto constrainedHeight = [this]( QskLayoutConstraint::Type, const QskControl*, qreal width ) { - return m_data->engine.heightForWidth( width ); + const QSizeF constraint( width, -1 ); + return m_data->engine.sizeHint( Qt::PreferredSize, constraint ).height(); }; return QskLayoutConstraint::constrainedMetric( @@ -433,7 +409,8 @@ qreal QskGridBox::widthForHeight( qreal height ) const auto constrainedWidth = [this]( QskLayoutConstraint::Type, const QskControl*, qreal height ) { - return m_data->engine.widthForHeight( height ); + const QSizeF constraint( -1, height ); + return m_data->engine.sizeHint( Qt::PreferredSize, constraint ).width(); }; return QskLayoutConstraint::constrainedMetric( @@ -456,7 +433,8 @@ void QskGridBox::itemChange( ItemChange change, const ItemChangeData& value ) { case ItemChildRemovedChange: { - removeItem( value.item ); + if ( !m_data->blockAutoRemove ) + removeItem( value.item ); break; } case QQuickItem::ItemVisibleHasChanged: diff --git a/src/layouts/QskGridBox.h b/src/layouts/QskGridBox.h index 7ec6ed07..5fee1fff 100644 --- a/src/layouts/QskGridBox.h +++ b/src/layouts/QskGridBox.h @@ -12,6 +12,9 @@ class QSK_EXPORT QskGridBox : public QskBox { Q_OBJECT + Q_PROPERTY( Qt::Alignment defaultAlignment READ defaultAlignment + WRITE setDefaultAlignment NOTIFY defaultAlignmentChanged ) + Q_PROPERTY( bool empty READ isEmpty() ) Q_PROPERTY( int count READ count ) @@ -21,14 +24,17 @@ class QSK_EXPORT QskGridBox : public QskBox explicit QskGridBox( QQuickItem* parent = nullptr ); ~QskGridBox() override; - Q_INVOKABLE void addItem( + Q_INVOKABLE int addItem( QQuickItem*, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment = Qt::Alignment() ); - Q_INVOKABLE void addItem( + Q_INVOKABLE int addItem( QQuickItem*, int row, int column, Qt::Alignment alignment = Qt::Alignment() ); + Q_INVOKABLE int addSpacer( qreal spacing, + int row, int column, int columnSpan = 1, int rowSpan = 1 ); + void removeItem( const QQuickItem* ); void removeAt( int index ); @@ -48,13 +54,11 @@ class QSK_EXPORT QskGridBox : public QskBox Q_INVOKABLE QQuickItem* itemAt( int row, int column ) const; Q_INVOKABLE int indexAt( int row, int column ) const; - Q_INVOKABLE int rowOfIndex( int index ) const; - Q_INVOKABLE int rowSpanOfIndex( int index ) const; + Q_INVOKABLE QRect gridOfIndex( int index ) const; + Q_INVOKABLE QRect effectiveGridOfIndex( int index ) const; - Q_INVOKABLE int columnOfIndex( int index ) const; - Q_INVOKABLE int columnSpanOfIndex( int index ) const; - - // spacings + void setDefaultAlignment( Qt::Alignment ); + Qt::Alignment defaultAlignment() const; void setSpacing( Qt::Orientations, qreal spacing ); void resetSpacing( Qt::Orientations ); @@ -70,24 +74,16 @@ class QSK_EXPORT QskGridBox : public QskBox void setHorizontalSpacing( qreal spacing ) { setSpacing( Qt::Horizontal, spacing ); } qreal horizontalSpacing() const { return spacing( Qt::Horizontal ); } - void setSpacing( qreal spacing ) { setSpacing( Qt::Horizontal | Qt::Vertical, spacing ); } - void setActive( bool ) {} bool isActive() const { return true; } - void setRowMinimumHeight( int column, qreal height ) - { setRowSizeHint( column, Qt::MinimumSize, height ); } + void setRowMinimumHeight( int row, qreal height ) + { setRowSizeHint( row, Qt::MinimumSize, height ); } void setColumnMaximumWidth( int column, qreal width ) { setColumnSizeHint( column, Qt::MaximumSize, width ); } #endif - Q_INVOKABLE void setRowSpacing( int row, qreal spacing ); - Q_INVOKABLE qreal rowSpacing( int row ) const; - - Q_INVOKABLE void setColumnSpacing( int column, qreal spacing ); - Q_INVOKABLE qreal columnSpacing( int column ) const; - // stretch factors Q_INVOKABLE void setRowStretchFactor( int row, int stretch ); Q_INVOKABLE int rowStretchFactor( int row ) const; @@ -108,12 +104,6 @@ class QSK_EXPORT QskGridBox : public QskBox // alignments - Q_INVOKABLE void setRowAlignment( int row, Qt::Alignment alignment ); - Q_INVOKABLE Qt::Alignment rowAlignment( int row ) const; - - Q_INVOKABLE void setColumnAlignment( int column, Qt::Alignment alignment ); - Q_INVOKABLE Qt::Alignment columnAlignment( int column ) const; - void setAlignment( const QQuickItem* item, Qt::Alignment alignment ); Qt::Alignment alignment( const QQuickItem* item ) const; @@ -129,6 +119,9 @@ class QSK_EXPORT QskGridBox : public QskBox void invalidate(); void clear( bool autoDelete = false ); + Q_SIGNALS: + void defaultAlignmentChanged(); + protected: bool event( QEvent* ) override; void geometryChangeEvent( QskGeometryChangeEvent* ) override; @@ -141,10 +134,10 @@ class QSK_EXPORT QskGridBox : public QskBox std::unique_ptr< PrivateData > m_data; }; -inline void QskGridBox::addItem( +inline int QskGridBox::addItem( QQuickItem* item, int row, int column, Qt::Alignment alignment ) { - addItem( item, row, column, 1, 1, alignment ); + return addItem( item, row, column, 1, 1, alignment ); } inline bool QskGridBox::isEmpty() const diff --git a/src/layouts/QskGridLayoutEngine.cpp b/src/layouts/QskGridLayoutEngine.cpp index c5303812..8d85efbf 100644 --- a/src/layouts/QskGridLayoutEngine.cpp +++ b/src/layouts/QskGridLayoutEngine.cpp @@ -4,214 +4,378 @@ *****************************************************************************/ #include "QskGridLayoutEngine.h" +#include "QskLayoutHint.h" #include "QskLayoutConstraint.h" +#include "QskLayoutChain.h" #include "QskSizePolicy.h" -#include "QskControl.h" #include "QskQuick.h" -QSK_QT_PRIVATE_BEGIN -#include -QSK_QT_PRIVATE_END +#include + +#include +#include + +static inline qreal qskSegmentLength( + const QskLayoutChain::Segments& s, int start, int end ) +{ + return s[ end ].start - s[ start ].start + s[ end ].length; +} namespace { - class LayoutStyleInfo final : public QAbstractLayoutStyleInfo + class Settings { public: - qreal spacing( Qt::Orientation ) const override + class Setting { - // later from the theme !! - return 5.0; - } - - qreal windowMargin( Qt::Orientation ) const override - { - // later from the theme !! - return 0; - } - - bool hasChangedCore() const override - { - return false; // never changes - } - }; - - class LayoutItem final : public QGridLayoutItem - { - public: - LayoutItem( QQuickItem* item, int row, int column, - int rowSpan, int columnSpan, Qt::Alignment alignment ) - : QGridLayoutItem( row, column, - qMax( rowSpan, 1 ), qMax( columnSpan, 1 ), alignment ) - , m_item( item ) - , m_retainSizeWhenHidden( false ) - , m_unlimitedRowSpan( rowSpan <= 0 ) - , m_unlimitedColumnSpan( columnSpan <= 0 ) - { - } - - QQuickItem* item() const - { - return m_item; - } - - void setGeometry( const QRectF& rect ) override - { - qskSetItemGeometry( m_item, rect ); - } - - QLayoutPolicy::Policy sizePolicy( Qt::Orientation orientation ) const override - { - const auto policy = QskLayoutConstraint::sizePolicy( m_item ); - return static_cast< QLayoutPolicy::Policy >( policy.policy( orientation ) ); - } - - QSizeF sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const override - { - return QskLayoutConstraint::sizeHint( m_item, which, constraint ); - } - - bool hasDynamicConstraint() const override - { - using namespace QskLayoutConstraint; - return constraintType( m_item ) != Unconstrained; - } - - Qt::Orientation dynamicConstraintOrientation() const override - { - Qt::Orientation orientation = Qt::Vertical; - - if ( auto control = qskControlCast( m_item ) ) + public: + inline bool isDefault() const { - const auto policy = control->sizePolicy().horizontalPolicy(); - - return ( policy == QskSizePolicy::Constrained ) - ? Qt::Horizontal : Qt::Vertical; + return m_stretch < 0 && m_hint.isDefault(); } - return orientation; - } + bool setStretch( int stretch ) + { + if ( stretch != m_stretch ) + { + m_stretch = stretch; + return true; + } + return false; + } - bool isIgnored() const override final + bool setHint( Qt::SizeHint which, qreal size ) + { + if ( size != m_hint.size( which ) ) + { + m_hint.setSize( which, size ); + return true; + } + return false; + } + + QskLayoutChain::CellData cell() const + { + QskLayoutChain::CellData cell; + cell.hint = m_hint.normalized(); + cell.stretch = m_stretch; + cell.canGrow = m_stretch != 0; + cell.isValid = true; + + return cell; + } + + inline int stretch() const { return m_stretch; } + inline QskLayoutHint hint() const { return m_hint; } + + int position = -1; + + private: + int m_stretch = -1; + QskLayoutHint m_hint; + }; + + void clear() { - if ( m_item && !qskIsVisibleToParent( m_item ) ) - return !m_retainSizeWhenHidden; - - return false; + m_settings.clear(); } - QLayoutPolicy::ControlTypes controlTypes( LayoutSide ) const override + bool setStretchAt( int index, int stretch ) { - return QLayoutPolicy::DefaultType; + auto setStretch = [stretch]( Setting& s ) + { return s.setStretch( stretch ); }; + + return setValueAt( index, setStretch ); } - bool retainSizeWhenHidden() const + bool setHintAt( int index, Qt::SizeHint which, qreal size ) { - return m_retainSizeWhenHidden; + auto setHint = [which, size]( Setting& s ) + { return s.setHint( which, size ); }; + + return setValueAt( index, setHint ); } - void setRetainSizeWhenHidden( bool on ) + Setting settingAt( int index ) const { - m_retainSizeWhenHidden = on; + auto it = lowerBound( index ); + if ( it != m_settings.end() ) + return *it; + + return Setting(); } - bool hasUnlimitedSpan() const - { - return m_unlimitedColumnSpan || m_unlimitedRowSpan; - } - - bool hasUnlimitedSpan( Qt::Orientation orientation ) const - { - return ( orientation == Qt::Horizontal ) - ? m_unlimitedColumnSpan : m_unlimitedRowSpan; - } + const std::vector< Setting >& settings() const { return m_settings; } private: - QQuickItem* m_item; - - bool m_retainSizeWhenHidden : 1; - bool m_unlimitedRowSpan : 1; - bool m_unlimitedColumnSpan : 1; - }; - - class LayoutEngine : public QGridLayoutEngine - { - public: - LayoutEngine() - : QGridLayoutEngine( Qt::AlignVCenter, false /*snapToPixelGrid*/ ) + inline bool setValueAt( int pos, + const std::function< bool( Setting& ) > modify ) { - /* - snapToPixelGrid rounds x/y, what might lead to losing a pixel. - F.e. when having a text in elideMode we end up with an elided text - because of this. - */ - } + if ( pos < 0 ) + return false; - LayoutItem* layoutItemAt( int index ) const - { - if ( index < 0 || index >= q_items.count() ) - return nullptr; + bool isModified; - return static_cast< LayoutItem* >( q_items[ index ] ); - } + auto it = lowerBound( pos ); - LayoutItem* layoutItemAt( int row, int column ) const - { - if ( row < 0 || row >= rowCount() || column < 0 || column >= columnCount() ) - return nullptr; - - return static_cast< LayoutItem* >( itemAt( row, column ) ); - } - - inline LayoutItem* layoutItemOf( const QQuickItem* item ) const - { - return layoutItemAt( indexOf( item ) ); - } - - int indexAt( int row, int column ) const - { - const auto item = layoutItemAt( row, column ); - if ( item ) - return q_items.indexOf( item ); - - return -1; - } - - int indexOf( const QQuickItem* item ) const - { - // linear search might become slow for many items, - // better introduce some sort of hash table TODO ... - - for ( int i = q_items.count() - 1; i >= 0; --i ) + if ( it != m_settings.end() && it->position == pos ) { - const auto layoutItem = static_cast< const LayoutItem* >( q_items[ i ] ); - if ( layoutItem->item() == item ) - return i; + isModified = modify( *it ); + + if ( isModified && it->isDefault() ) + m_settings.erase( it ); + } + else + { + Setting setting; + isModified = modify( setting ); + + if ( isModified ) + { + setting.position = pos; + m_settings.insert( it, setting ); + } } - return -1; + return isModified; } - qreal spacing( Qt::Orientation orientation ) const + inline std::vector< Setting >::iterator lowerBound( int index ) const { - const LayoutStyleInfo styleInfo; - return QGridLayoutEngine::spacing( orientation, &styleInfo ); + auto cmp = []( const Setting& setting, const int& pos ) + { return setting.position < pos; }; + + auto& settings = const_cast< std::vector< Setting >& >( m_settings ); + return std::lower_bound( settings.begin(), settings.end(), index, cmp ); } + + std::vector< Setting > m_settings; // a flat map }; } +namespace +{ + class Element + { + public: + Element( QQuickItem*, const QRect&, Qt::Alignment ); + Element( qreal spacing, const QRect& ); + + Element& operator=( const Element& ); + + qreal spacer() const; + QQuickItem* item() const; + + Qt::Alignment alignment() const; + void setAlignment( Qt::Alignment ); + + bool retainSizeWhenHidden() const; + void setRetainSizeWhenHidden( bool ); + + QRect grid() const; + void setGrid( const QRect& ); + + QRect minimumGrid() const; + + bool isIgnored() const; + + QskLayoutChain::CellData cell( + Qt::Orientation, qreal constraint ) const; + + void transpose(); + + private: + + union + { + QQuickItem* m_item; + qreal m_spacer; + }; + + QRect m_grid; + + unsigned int m_alignment : 8; + bool m_isSpacer : 1; + bool m_retainSizeWhenHidden : 1; + }; +} + +Element::Element( QQuickItem* item, + const QRect& grid, Qt::Alignment alignment ) + : m_item( item ) + , m_grid( grid ) + , m_alignment(alignment) + , m_isSpacer( false ) + , m_retainSizeWhenHidden( false ) +{ +} + +Element::Element( qreal spacing, const QRect& grid ) + : m_spacer( spacing ) + , m_grid( grid ) + , m_alignment( 0 ) + , m_isSpacer( true ) + , m_retainSizeWhenHidden( false ) +{ +} + +Element& Element::operator=( const Element& other ) +{ + m_isSpacer = other.m_isSpacer; + + if ( other.m_isSpacer ) + m_spacer = other.m_spacer; + else + m_item = other.m_item; + + m_grid = other.m_grid; + m_alignment = other.m_alignment; + m_retainSizeWhenHidden = other.m_retainSizeWhenHidden; + + return *this; +} + +inline qreal Element::spacer() const +{ + return m_isSpacer ? m_spacer : -1.0; +} + +inline QQuickItem* Element::item() const +{ + return m_isSpacer ? nullptr : m_item; +} + +inline Qt::Alignment Element::alignment() const +{ + return static_cast< Qt::Alignment >( m_alignment ); +} + +void Element::setAlignment( Qt::Alignment alignment ) +{ + m_alignment = alignment; +} + +inline bool Element::retainSizeWhenHidden() const +{ + return m_retainSizeWhenHidden; +} + +void Element::setRetainSizeWhenHidden( bool on ) +{ + m_retainSizeWhenHidden = on; +} + +QRect Element::grid() const +{ + return m_grid; +} + +void Element::setGrid( const QRect& grid ) +{ + m_grid = grid; +} + +QRect Element::minimumGrid() const +{ + return QRect( m_grid.left(), m_grid.top(), + qMax( m_grid.width(), 1 ), qMax( m_grid.height(), 1 ) ); +} + +bool Element::isIgnored() const +{ + if ( !m_isSpacer && !m_retainSizeWhenHidden ) + return !qskIsVisibleToParent( m_item ); + + return false; +} + +QskLayoutChain::CellData Element::cell( + Qt::Orientation orientation, qreal constraint ) const +{ + const auto policy = QskLayoutConstraint::sizePolicy( + m_item ).policy( orientation ); + + QskLayoutChain::CellData cell; + cell.isValid = true; + cell.canGrow = policy & QskSizePolicy::GrowFlag; + + if ( policy & QskSizePolicy::ExpandFlag ) + cell.stretch = 1; + + cell.hint = QskLayoutConstraint::layoutHint( m_item, orientation, constraint ); + + return cell; +} + +void Element::transpose() +{ + m_grid.setRect( m_grid.top(), m_grid.left(), + m_grid.height(), m_grid.width() ); +} + class QskGridLayoutEngine::PrivateData { public: - /* - For the moment we use QGridLayoutEngine, but sooner and later - it should be replaced by a new implementation, that uses - the same backend as QskLinearLayoutEngine. - */ - LayoutEngine qengine; - unsigned int unlimitedSpanned = 0; -}; + inline Element* elementAt( int index ) const + { + const int count = this->elements.size(); + if ( index < 0 || index >= count ) + return nullptr; + return const_cast< Element* >( &this->elements[index] ); + } + + int insertElement( QQuickItem* item, qreal spacing, + QRect grid, Qt::Alignment alignment ) + { + // -1 means unlimited, while 0 does not make any sense + if ( grid.width() == 0 ) + grid.setWidth( 1 ); + + if ( grid.height() == 0 ) + grid.setHeight( 1 ); + + if ( item ) + elements.push_back( Element( item, grid, alignment ) ); + else + elements.push_back( Element( spacing, grid ) ); + + grid = effectiveGrid( elements.back() ); + + rowCount = qMax( rowCount, grid.bottom() + 1 ); + columnCount = qMax( columnCount, grid.right() + 1 ); + + return this->elements.size() - 1; + } + + QRect effectiveGrid( const Element& element ) const + { + QRect r = element.grid(); + + if ( r.width() <= 0 ) + r.setRight( qMax( this->columnCount - 1, r.left() ) ); + + if ( r.height() <= 0 ) + r.setBottom( qMax( this->rowCount - 1, r.top() ) ); + + return r; + } + + Settings& settings( Qt::Orientation orientation ) const + { + auto that = const_cast< PrivateData* >( this ); + return ( orientation == Qt::Horizontal ) + ? that->columnSettings : that->rowSettings; + } + + std::vector< Element > elements; + + Settings rowSettings; + Settings columnSettings; + + int rowCount = 0; + int columnCount = 0; +}; QskGridLayoutEngine::QskGridLayoutEngine() : m_data( new PrivateData() ) @@ -222,333 +386,330 @@ QskGridLayoutEngine::~QskGridLayoutEngine() { } -void QskGridLayoutEngine::setGeometries( const QRectF rect ) +int QskGridLayoutEngine::count() const { - const LayoutStyleInfo styleInfo; - m_data->qengine.setGeometries( rect, &styleInfo ); + return m_data->elements.size(); } -void QskGridLayoutEngine::invalidate() +bool QskGridLayoutEngine::setStretchFactor( + int pos, int stretch, Qt::Orientation orientation ) { - m_data->qengine.invalidate(); -} + if ( pos < 0 ) + return false; -void QskGridLayoutEngine::setVisualDirection( Qt::LayoutDirection direction ) -{ - m_data->qengine.setVisualDirection( direction ); -} + if ( stretch < 0 ) + stretch = -1; -Qt::LayoutDirection QskGridLayoutEngine::visualDirection() const -{ - return m_data->qengine.visualDirection(); -} - -int QskGridLayoutEngine::itemCount() const -{ - return m_data->qengine.itemCount(); -} - -int QskGridLayoutEngine::rowCount() const -{ - return m_data->qengine.rowCount(); -} - -int QskGridLayoutEngine::columnCount() const -{ - return m_data->qengine.columnCount(); -} - -void QskGridLayoutEngine::insertItem( QQuickItem* item, - int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment ) -{ - auto& qengine = m_data->qengine; - - auto layoutItem = new LayoutItem( - item, row, column, rowSpan, columnSpan, alignment ); - - const bool isExpanding = ( layoutItem->lastColumn() >= qengine.columnCount() ) || - ( layoutItem->lastRow() >= qengine.rowCount() ); - - qengine.insertItem( layoutItem, -1 ); - - if ( isExpanding ) + if ( m_data->settings( orientation ).setStretchAt( pos, stretch ) ) { - // the new item has extended the number of rows/columns and - // we need to adjust all items without fixed spanning - - if ( m_data->unlimitedSpanned > 0 ) - adjustSpans( columnCount(), rowCount() ); + invalidate(); + return true; } - if ( layoutItem->hasUnlimitedSpan() ) - { - // the item itself might need to be adjusted - - if ( layoutItem->hasUnlimitedSpan( Qt::Horizontal ) ) - { - const int span = columnCount() - layoutItem->firstColumn(); - layoutItem->setRowSpan( span, Qt::Horizontal ); - } - - if ( layoutItem->hasUnlimitedSpan( Qt::Vertical ) ) - { - const int span = rowCount() - layoutItem->firstRow(); - layoutItem->setRowSpan( span, Qt::Vertical ); - } - - m_data->unlimitedSpanned++; - } -} - -void QskGridLayoutEngine::removeAt( int index ) -{ - auto& qengine = m_data->qengine; - - auto layoutItem = qengine.layoutItemAt( index ); - if ( layoutItem == nullptr ) - return; - - qengine.removeItem( layoutItem ); - - if ( layoutItem->hasUnlimitedSpan() ) - m_data->unlimitedSpanned--; - - // cleanup rows/columns - - const QSize cells = requiredCells(); - - const int numPendingColumns = qengine.columnCount() - cells.width(); - const int numPendingRows = qengine.rowCount() - cells.height(); - - if ( numPendingColumns > 0 || numPendingRows > 0 ) - { - if ( m_data->unlimitedSpanned > 0 ) - adjustSpans( cells.height(), cells.width() ); - - qengine.removeRows( cells.width(), numPendingColumns, Qt::Horizontal ); - qengine.removeRows( cells.height(), numPendingRows, Qt::Vertical ); - } - - delete layoutItem; -} - -QQuickItem* QskGridLayoutEngine::itemAt( int index ) const -{ - if ( const auto layoutItem = m_data->qengine.layoutItemAt( index ) ) - return layoutItem->item(); - - return nullptr; -} - -QQuickItem* QskGridLayoutEngine::itemAt( int row, int column ) const -{ - if ( const auto layoutItem = m_data->qengine.layoutItemAt( row, column ) ) - return layoutItem->item(); - - return nullptr; -} - -int QskGridLayoutEngine::indexAt( int row, int column ) const -{ - return m_data->qengine.indexAt( row, column ); -} - -int QskGridLayoutEngine::indexOf( const QQuickItem* item ) const -{ - if ( item == nullptr ) - return -1; - - return m_data->qengine.indexOf( item ); -} - -int QskGridLayoutEngine::rowOfIndex( int index ) const -{ - if ( auto layoutItem = m_data->qengine.layoutItemAt( index ) ) - return layoutItem->firstRow(); - - return -1; -} - -int QskGridLayoutEngine::rowSpanOfIndex( int index ) const -{ - if ( auto layoutItem = m_data->qengine.layoutItemAt( index ) ) - return layoutItem->rowSpan(); - - return 0; -} - -int QskGridLayoutEngine::columnOfIndex( int index ) const -{ - if ( auto layoutItem = m_data->qengine.layoutItemAt( index ) ) - return layoutItem->firstColumn(); - - return -1; -} - -int QskGridLayoutEngine::columnSpanOfIndex( int index ) const -{ - if ( auto layoutItem = m_data->qengine.layoutItemAt( index ) ) - return layoutItem->columnSpan(); - - return 0; -} - -void QskGridLayoutEngine::setSpacing( - Qt::Orientation orientation, qreal spacing ) -{ - m_data->qengine.setSpacing( spacing, orientation ); -} - -qreal QskGridLayoutEngine::spacing( Qt::Orientation orientation ) const -{ - return m_data->qengine.spacing( orientation ); -} - -void QskGridLayoutEngine::setSpacingAt( - Qt::Orientation orientation, int cell, qreal spacing ) -{ - // is this a spacer ??? - m_data->qengine.setRowSpacing( cell, spacing, orientation ); -} - -qreal QskGridLayoutEngine::spacingAt( - Qt::Orientation orientation, int cell ) const -{ - return m_data->qengine.rowSpacing( cell, orientation ); -} - -void QskGridLayoutEngine::setStretchFactorAt( - Qt::Orientation orientation, int cell, int stretch ) -{ - m_data->qengine.setRowStretchFactor( cell, stretch, orientation ); -} - -int QskGridLayoutEngine::stretchFactorAt( Qt::Orientation orientation, int cell ) -{ - return m_data->qengine.rowStretchFactor( cell, orientation ); -} - -void QskGridLayoutEngine::setAlignmentAt( - Qt::Orientation orientation, int cell, Qt::Alignment alignment ) -{ - m_data->qengine.setRowAlignment( cell, alignment, orientation ); -} - -Qt::Alignment QskGridLayoutEngine::alignmentAt( - Qt::Orientation orientation, int cell ) const -{ - return m_data->qengine.rowAlignment( cell, orientation ); -} - -void QskGridLayoutEngine::setAlignmentOf( - const QQuickItem* item, Qt::Alignment alignment ) -{ - if ( auto layoutItem = m_data->qengine.layoutItemOf( item ) ) - layoutItem->setAlignment( alignment ); -} - -Qt::Alignment QskGridLayoutEngine::alignmentOf( const QQuickItem* item ) const -{ - if ( const auto layoutItem = m_data->qengine.layoutItemOf( item ) ) - return layoutItem->alignment(); - - return Qt::Alignment(); -} - -void QskGridLayoutEngine::setRetainSizeWhenHiddenOf( const QQuickItem* item, bool on ) -{ - if ( auto layoutItem = m_data->qengine.layoutItemOf( item ) ) - layoutItem->setRetainSizeWhenHidden( on ); -} - -bool QskGridLayoutEngine::retainSizeWhenHiddenOf( const QQuickItem* item ) const -{ - if ( const auto layoutItem = m_data->qengine.layoutItemOf( item ) ) - return layoutItem->retainSizeWhenHidden(); - return false; } -void QskGridLayoutEngine::setRowSizeHint( int row, Qt::SizeHint which, qreal height ) +int QskGridLayoutEngine::stretchFactor( + int pos, Qt::Orientation orientation ) const { - m_data->qengine.setRowSizeHint( which, row, height, Qt::Vertical ); + const auto setting = m_data->settings( orientation ).settingAt( pos ); + return ( setting.position == pos ) ? setting.stretch() : 0; +} + +bool QskGridLayoutEngine::setRowSizeHint( + int row, Qt::SizeHint which, qreal height ) +{ + if ( m_data->rowSettings.setHintAt( row, which, height ) ) + { + invalidate(); + return true; + } + + return false; } qreal QskGridLayoutEngine::rowSizeHint( int row, Qt::SizeHint which ) const { - return m_data->qengine.rowSizeHint( which, row, Qt::Vertical ); + const auto& settings = m_data->rowSettings; + return settings.settingAt( row ).hint().size( which ); } -void QskGridLayoutEngine::setColumnSizeHint( int column, Qt::SizeHint which, qreal width ) +bool QskGridLayoutEngine::setColumnSizeHint( + int column, Qt::SizeHint which, qreal width ) { - m_data->qengine.setRowSizeHint( which, column, width, Qt::Horizontal ); + if ( m_data->columnSettings.setHintAt( column, which, width ) ) + { + invalidate(); + return true; + } + + return false; } qreal QskGridLayoutEngine::columnSizeHint( int column, Qt::SizeHint which ) const { - return m_data->qengine.rowSizeHint( which, column, Qt::Horizontal ); + const auto& settings = m_data->columnSettings; + return settings.settingAt( column ).hint().size( which ); } -QSizeF QskGridLayoutEngine::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const +bool QskGridLayoutEngine::setAlignmentAt( int index, Qt::Alignment alignment ) { - const LayoutStyleInfo styleInfo; - return m_data->qengine.sizeHint( which, constraint, &styleInfo ); -} - -qreal QskGridLayoutEngine::widthForHeight( qreal height ) const -{ - const QSizeF constraint( -1, height ); - return sizeHint( Qt::PreferredSize, constraint ).width(); -} - -qreal QskGridLayoutEngine::heightForWidth( qreal width ) const -{ - const QSizeF constraint( width, -1 ); - return sizeHint( Qt::PreferredSize, constraint ).height(); -} - -qreal QskGridLayoutEngine::defaultSpacing( Qt::Orientation orientation ) -{ - return LayoutStyleInfo().spacing( orientation ); -} - -QSize QskGridLayoutEngine::requiredCells() const -{ - int lastRow = -1; - int lastColumn = -1; - - for ( int i = 0; i < m_data->qengine.itemCount(); i++ ) + if ( auto element = m_data->elementAt( index ) ) { - const auto layoutItem = m_data->qengine.layoutItemAt( i ); - if ( layoutItem->isIgnored() ) + if ( alignment != element->alignment() ) + element->setAlignment( alignment ); + + return true; + } + + return false; +} + +Qt::Alignment QskGridLayoutEngine::alignmentAt( int index ) const +{ + if ( const auto element = m_data->elementAt( index ) ) + return element->alignment(); + + return Qt::Alignment(); +} + +bool QskGridLayoutEngine::setRetainSizeWhenHiddenAt( int index, bool on ) +{ + if ( auto element = m_data->elementAt( index ) ) + { + if ( on != element->retainSizeWhenHidden() ) + { + const bool isIgnored = element->isIgnored(); + element->setRetainSizeWhenHidden( on ); + + if ( isIgnored != element->isIgnored() ) + { + invalidate(); + return true; + } + } + } + + return false; +} + +bool QskGridLayoutEngine::retainSizeWhenHiddenAt( int index ) const +{ + if ( const auto element = m_data->elementAt( index ) ) + return element->retainSizeWhenHidden(); + + return false; +} + +int QskGridLayoutEngine::insertItem( QQuickItem* item, + const QRect& grid, Qt::Alignment alignment ) +{ + invalidate(); + return m_data->insertElement( item, -1, grid, alignment ); +} + +int QskGridLayoutEngine::insertSpacer( qreal spacing, const QRect& grid ) +{ + spacing = qMax( spacing, 0.0 ); + return m_data->insertElement( nullptr, spacing, grid, Qt::Alignment() ); +} + +bool QskGridLayoutEngine::removeAt( int index ) +{ + const auto element = m_data->elementAt( index ); + if ( element == nullptr ) + return false; + + const auto grid = element->minimumGrid(); + + auto& elements = m_data->elements; + elements.erase( elements.begin() + index ); + + // doing a lazy recalculation instead ?? + + if ( grid.bottom() >= m_data->rowCount + || grid.right() >= m_data->columnCount ) + { + int maxRow = -1; + int maxColumn = -1; + + for ( const auto& element : elements ) + { + const auto grid = element.minimumGrid(); + + maxRow = qMax( maxRow, grid.bottom() ); + maxColumn = qMax( maxColumn, grid.right() ); + } + + m_data->rowCount = maxRow + 1; + m_data->columnCount = maxColumn + 1; + } + + invalidate(); + return true; +} + +bool QskGridLayoutEngine::clear() +{ + m_data->elements.clear(); + m_data->rowSettings.clear(); + m_data->columnSettings.clear(); + + invalidate(); + return true; +} + +int QskGridLayoutEngine::indexAt( int row, int column ) const +{ + if ( row < m_data->rowCount && column < m_data->columnCount ) + { + for ( uint i = 0; i < m_data->elements.size(); i++ ) + { + const auto grid = m_data->effectiveGrid( m_data->elements[i] ); + if ( grid.contains( column, row ) ) + return i; + } + } + + return -1; +} + +QQuickItem* QskGridLayoutEngine::itemAt( int index ) const +{ + if ( const auto element = m_data->elementAt( index ) ) + return element->item(); + + return nullptr; +} + +qreal QskGridLayoutEngine::spacerAt( int index ) const +{ + if ( const auto element = m_data->elementAt( index ) ) + return element->spacer(); + + return -1.0; +} + +QQuickItem* QskGridLayoutEngine::itemAt( int row, int column ) const +{ + return itemAt( indexAt( row, column ) ); +} + +bool QskGridLayoutEngine::setGridAt( int index, const QRect& grid ) +{ + if ( auto element = m_data->elementAt( index ) ) + { + if ( element->grid() != grid ) + { + element->setGrid( grid ); + invalidate(); + + return true; + } + } + + return false; +} + +QRect QskGridLayoutEngine::gridAt( int index ) const +{ + if ( auto element = m_data->elementAt( index ) ) + return element->grid(); + + return QRect(); +} + +QRect QskGridLayoutEngine::effectiveGridAt( int index ) const +{ + if ( auto element = m_data->elementAt( index ) ) + return m_data->effectiveGrid( *element ); + + return QRect(); +} + +void QskGridLayoutEngine::invalidateElementCache() +{ +} + +void QskGridLayoutEngine::layoutItems() +{ + for ( const auto& element : m_data->elements ) + { + if ( !element.isIgnored() ) + { + if ( auto item = element.item() ) + { + const auto grid = m_data->effectiveGrid( element ); + layoutItem( item, grid, element.alignment() ); + } + } + } +} + +void QskGridLayoutEngine::transpose() +{ + for ( auto& element : m_data->elements ) + element.transpose(); + + qSwap( m_data->columnSettings, m_data->rowSettings ); + invalidate(); +} + +int QskGridLayoutEngine::effectiveCount( + Qt::Orientation orientation ) const +{ + return ( orientation == Qt::Horizontal ) + ? m_data->columnCount : m_data->rowCount; +} + +void QskGridLayoutEngine::setupChain( Qt::Orientation orientation, + const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const +{ + /* + We collect all information from the simple elements first + befora adding those that occupy more than one cell + */ + QVarLengthArray< const Element* > postponed; + + for ( const auto& element : m_data->elements ) + { + if ( element.isIgnored() ) continue; - const int col = layoutItem->hasUnlimitedSpan( Qt::Horizontal ) - ? layoutItem->firstColumn() + 1 : layoutItem->lastColumn(); + auto grid = m_data->effectiveGrid( element ); + if ( orientation == Qt::Horizontal ) + grid.setRect( grid.y(), grid.x(), grid.height(), grid.width() ); - if ( col > lastColumn ) - lastColumn = col; + if ( grid.height() == 1 ) + { + qreal constraint = -1.0; + if ( !constraints.isEmpty() ) + constraint = qskSegmentLength( constraints, grid.left(), grid.right() ); - const int row = layoutItem->hasUnlimitedSpan( Qt::Vertical ) - ? layoutItem->firstRow() + 1 : layoutItem->lastRow(); - - if ( row > lastRow ) - lastRow = row; + chain.expandCell( grid.top(), element.cell( orientation, constraint ) ); + } + else + { + postponed += &element; + } } - return QSize( lastColumn + 1, lastRow + 1 ); -} + const auto& settings = m_data->settings( orientation ); -void QskGridLayoutEngine::adjustSpans( int numRows, int numColumns ) -{ - for ( int i = 0; i < m_data->qengine.itemCount(); i++ ) + for ( const auto& setting : settings.settings() ) + chain.narrowCell( setting.position, setting.cell() ); + + for ( const auto element : postponed ) { - auto layoutItem = m_data->qengine.layoutItemAt( i ); + auto grid = m_data->effectiveGrid( *element ); + if ( orientation == Qt::Horizontal ) + grid.setRect( grid.y(), grid.x(), grid.height(), grid.width() ); - if ( layoutItem->hasUnlimitedSpan( Qt::Horizontal ) ) - layoutItem->setRowSpan( numColumns - layoutItem->firstColumn(), Qt::Horizontal ); + qreal constraint = -1.0; + if ( !constraints.isEmpty() ) + constraint = qskSegmentLength( constraints, grid.left(), grid.right() ); - if ( layoutItem->hasUnlimitedSpan( Qt::Vertical ) ) - layoutItem->setRowSpan( numRows - layoutItem->firstRow(), Qt::Vertical ); + chain.expandCells( grid.top(), grid.height(), + element->cell( orientation, constraint ) ); } } diff --git a/src/layouts/QskGridLayoutEngine.h b/src/layouts/QskGridLayoutEngine.h index 51cf6530..d5231a82 100644 --- a/src/layouts/QskGridLayoutEngine.h +++ b/src/layouts/QskGridLayoutEngine.h @@ -7,89 +7,68 @@ #define QSK_GRID_LAYOUT_ENGINE_H #include "QskGlobal.h" +#include "QskLayoutEngine2D.h" #include -#include #include class QQuickItem; +class QSizeF; +class QRectF; -class QskGridLayoutEngine +class QskGridLayoutEngine : public QskLayoutEngine2D { public: QskGridLayoutEngine(); - ~QskGridLayoutEngine(); + ~QskGridLayoutEngine() override; - void setGeometries( const QRectF ); - void invalidate(); + int count() const override final; - void setVisualDirection( Qt::LayoutDirection ); - Qt::LayoutDirection visualDirection() const; + bool setStretchFactor( int pos, int stretch, Qt::Orientation ); + int stretchFactor( int pos, Qt::Orientation ) const; - int itemCount() const; - - int rowCount() const; - int columnCount() const; - - void insertItem( QQuickItem* item, - int row, int column, int rowSpan, int columnSpan, - Qt::Alignment ); - - void removeAt( int index ); - - int indexAt( int row, int column ) const; - int indexOf( const QQuickItem* item ) const; - - QQuickItem* itemAt( int index ) const; - QQuickItem* itemAt( int row, int column ) const; - - int rowOfIndex( int index ) const; - int rowSpanOfIndex( int index ) const; - - int columnOfIndex( int index ) const; - int columnSpanOfIndex( int index ) const; - - void setSpacing( Qt::Orientation, qreal spacing ); - qreal spacing( Qt::Orientation ) const; - - void setSpacingAt( Qt::Orientation, int cell, qreal spacing ); - qreal spacingAt( Qt::Orientation, int cell ) const; - - void setStretchFactorAt( Qt::Orientation, int cell, int stretch ); - int stretchFactorAt( Qt::Orientation, int cell ); - - void setAlignmentAt( Qt::Orientation, int cell, Qt::Alignment ); - Qt::Alignment alignmentAt( Qt::Orientation, int cell ) const; - - void setAlignmentOf( const QQuickItem*, Qt::Alignment ); - Qt::Alignment alignmentOf( const QQuickItem* ) const; - - void setRetainSizeWhenHiddenOf( const QQuickItem*, bool on ); - bool retainSizeWhenHiddenOf( const QQuickItem* ) const; - - void setRowSizeHint( int row, Qt::SizeHint, qreal height ); + bool setRowSizeHint( int row, Qt::SizeHint, qreal height ); qreal rowSizeHint( int row, Qt::SizeHint ) const; - void setColumnSizeHint( int column, Qt::SizeHint, qreal width ); + bool setColumnSizeHint( int column, Qt::SizeHint, qreal width ); qreal columnSizeHint( int column, Qt::SizeHint ) const; - QSizeF sizeHint( Qt::SizeHint, const QSizeF& constraint = QSizeF() ) const; + int insertItem( QQuickItem*, const QRect& grid, Qt::Alignment ); + int insertSpacer( qreal spacing, const QRect& grid ); - qreal widthForHeight( qreal height ) const; - qreal heightForWidth( qreal width ) const; + bool removeAt( int index ); + bool clear(); - static qreal defaultSpacing( Qt::Orientation ); + QQuickItem* itemAt( int index ) const override final; + qreal spacerAt( int index ) const override final; -#if 1 - QSize requiredCells() const; - void adjustSpans( int numRows, int numColumns ); -#endif + QQuickItem* itemAt( int row, int column ) const; + int indexAt( int row, int column ) const; + + bool setGridAt( int index, const QRect& ); + QRect gridAt( int index ) const; + + QRect effectiveGridAt( int index ) const; + + bool setRetainSizeWhenHiddenAt( int index, bool on ); + bool retainSizeWhenHiddenAt( int index ) const; + + bool setAlignmentAt( int index, Qt::Alignment ); + Qt::Alignment alignmentAt( int index ) const; + + void transpose(); private: - Q_DISABLE_COPY(QskGridLayoutEngine) + void layoutItems() override; + int effectiveCount( Qt::Orientation ) const override; + + void invalidateElementCache() override; + + void setupChain( Qt::Orientation, + const QskLayoutChain::Segments&, QskLayoutChain& ) const override; class PrivateData; - std::unique_ptr< PrivateData > m_data; + PrivateData* m_data; }; #endif diff --git a/src/layouts/QskLayoutChain.cpp b/src/layouts/QskLayoutChain.cpp index 8812a9c3..2d435c94 100644 --- a/src/layouts/QskLayoutChain.cpp +++ b/src/layouts/QskLayoutChain.cpp @@ -13,6 +13,8 @@ #include #endif +#include + QskLayoutChain::QskLayoutChain() { } @@ -29,13 +31,13 @@ void QskLayoutChain::invalidate() void QskLayoutChain::reset( int count, qreal constraint ) { - m_cells.assign( count, Cell() ); + m_cells.fill( CellData(), count ); m_constraint = constraint; m_sumStretches = 0; m_validCells = 0; } -void QskLayoutChain::expandTo( int index, const Cell& newCell ) +void QskLayoutChain::narrowCell( int index, const CellData& newCell ) { if ( !newCell.isValid ) return; @@ -45,12 +47,109 @@ void QskLayoutChain::expandTo( int index, const Cell& newCell ) if ( !cell.isValid ) { cell = newCell; + cell.stretch = qMax( cell.stretch, 0 ); + m_validCells++; + } + else + { + cell.canGrow &= newCell.canGrow; + if ( newCell.stretch >= 0 ) + cell.stretch = qMax( cell.stretch, newCell.stretch ); + + if ( !newCell.hint.isDefault() ) + { + cell.hint.setSizes( + qMax( cell.hint.minimum(), newCell.hint.minimum() ), + qMax( cell.hint.preferred(), newCell.hint.preferred() ), + qMin( cell.hint.maximum(), newCell.hint.maximum() ) + ); + + cell.hint.normalize(); + } + } +} + +void QskLayoutChain::expandCell( int index, const CellData& newCell ) +{ + if ( !newCell.isValid ) + return; + + auto& cell = m_cells[ index ]; + + if ( !cell.isValid ) + { + cell = newCell; + cell.stretch = qMax( cell.stretch, 0 ); + m_validCells++; } else { cell.canGrow |= newCell.canGrow; cell.stretch = qMax( cell.stretch, newCell.stretch ); - cell.hint.expandTo( newCell.hint ); + + cell.hint.setSizes( + qMax( cell.hint.minimum(), newCell.hint.minimum() ), + qMax( cell.hint.preferred(), newCell.hint.preferred() ), + qMax( cell.hint.maximum(), newCell.hint.maximum() ) + ); + } +} + +void QskLayoutChain::expandCells( + int index, int count, const CellData& multiCell ) +{ + QskLayoutChain chain; + chain.reset( count, -1 ); + + for ( int i = 0; i < count; i++ ) + { + chain.expandCell( i, m_cells[ index + i ] ); + + auto& cell = chain.m_cells[ i ]; +#if 1 + // what to do now ?? + if ( !cell.isValid ) + { + cell.isValid = true; + cell.canGrow = multiCell.canGrow; + cell.stretch = qMax( cell.stretch, 0 ); + } +#endif + } + chain.m_validCells = count; + + QVarLengthArray< QskLayoutHint > hints( count ); + + const auto& hint = multiCell.hint; + const auto chainHint = chain.boundingHint(); + + if ( hint.minimum() > chainHint.minimum() ) + { + const auto segments = chain.segments( hint.minimum() ); + for ( int i = 0; i < count; i++ ) + hints[i].setMinimum( segments[i].length ); + } + + if ( hint.preferred() > chainHint.preferred() ) + { + const auto segments = chain.segments( hint.preferred() ); + for ( int i = 0; i < count; i++ ) + hints[i].setPreferred( segments[i].length ); + } + + if ( hint.maximum() < chainHint.maximum() ) + { + const auto segments = chain.segments( hint.maximum() ); + for ( int i = 0; i < count; i++ ) + hints[i].setMaximum( segments[i].length ); + } + + for ( int i = 0; i < count; i++ ) + { + auto cell = multiCell; + cell.hint = hints[i]; + + expandCell( index + i, cell ); } } @@ -119,24 +218,24 @@ bool QskLayoutChain::setSpacing( qreal spacing ) return false; } -QVector< QskLayoutChain::Range > QskLayoutChain::geometries( qreal size ) const +QskLayoutChain::Segments QskLayoutChain::segments( qreal size ) const { if ( m_validCells == 0 ) - return QVector< Range >(); + return Segments(); - QVector< Range > ranges; + Segments segments; if ( size <= m_boundingHint.minimum() ) { - ranges = distributed( Qt::MinimumSize, 0.0, 0.0 ); + segments = distributed( Qt::MinimumSize, 0.0, 0.0 ); } else if ( size < m_boundingHint.preferred() ) { - ranges = minimumExpanded( size ); + segments = minimumExpanded( size ); } else if ( size <= m_boundingHint.maximum() ) { - ranges = preferredStretched( size ); + segments = preferredStretched( size ); } else { @@ -145,70 +244,70 @@ QVector< QskLayoutChain::Range > QskLayoutChain::geometries( qreal size ) const qreal offset = 0.0; qreal extra = 0.0;; - if ( m_extraSpacingAt == Qt::LeftEdge ) + switch( m_extraSpacingAt ) { - offset = padding; - } - else if ( m_extraSpacingAt == Qt::RightEdge ) - { - offset = 0.0; - } - else if ( m_extraSpacingAt == ( Qt::LeftEdge | Qt::RightEdge ) ) - { - offset = 0.5 * padding; - } - else - { - extra = padding / m_validCells; + case Leading: + offset = padding; + break; + + case Trailing: + break; + + case Leading | Trailing: + offset = 0.5 * padding; + break; + + default: + extra = padding / m_validCells; } - ranges = distributed( Qt::MaximumSize, offset, extra ); + segments = distributed( Qt::MaximumSize, offset, extra ); } - return ranges; + return segments; } -QVector< QskLayoutChain::Range > QskLayoutChain::distributed( +QskLayoutChain::Segments QskLayoutChain::distributed( int which, qreal offset, const qreal extra ) const { qreal fillSpacing = 0.0; - QVector< Range > ranges( m_cells.size() ); + Segments segments( m_cells.size() ); - for ( int i = 0; i < ranges.count(); i++ ) + for ( int i = 0; i < segments.count(); i++ ) { const auto& cell = m_cells[i]; - auto& range = ranges[i]; + auto& segment = segments[i]; if ( !cell.isValid ) { - range.start = offset; - range.length = 0.0; + segment.start = offset; + segment.length = 0.0; } else { offset += fillSpacing; fillSpacing = m_spacing; - range.start = offset; - range.length = cell.hint.size( which ) + extra; + segment.start = offset; + segment.length = cell.hint.size( which ) + extra; - offset += range.length; + offset += segment.length; } } - return ranges; + return segments; } -QVector< QskLayoutChain::Range > QskLayoutChain::minimumExpanded( qreal size ) const +QskLayoutChain::Segments QskLayoutChain::minimumExpanded( qreal size ) const { - QVector< Range > ranges( m_cells.size() ); + Segments segments( m_cells.size() ); qreal fillSpacing = 0.0; qreal offset = 0.0; /* - We have different options how to distribute the availabe space + We have different options how to distribute the available space - according to the preferred sizes @@ -231,7 +330,7 @@ QVector< QskLayoutChain::Range > QskLayoutChain::minimumExpanded( qreal size ) c const qreal desired = m_boundingHint.preferred() - m_boundingHint.minimum(); const qreal available = size - m_boundingHint.minimum(); - for ( uint i = 0; i < m_cells.size(); i++ ) + for ( int i = 0; i < m_cells.size(); i++ ) { const auto& cell = m_cells[i]; if ( !cell.isValid ) @@ -247,67 +346,67 @@ QVector< QskLayoutChain::Range > QskLayoutChain::minimumExpanded( qreal size ) c } } - for ( uint i = 0; i < m_cells.size(); i++ ) + for ( int i = 0; i < m_cells.size(); i++ ) { const auto& cell = m_cells[i]; - auto& range = ranges[i]; + auto& segment = segments[i]; if ( !cell.isValid ) { - range.start = offset; - range.length = 0.0; + segment.start = offset; + segment.length = 0.0; } else { offset += fillSpacing; fillSpacing = m_spacing; - range.start = offset; - range.length = cell.hint.minimum() + segment.start = offset; + segment.length = cell.hint.minimum() + available * ( factors[i] / sumFactors ); - offset += range.length; + offset += segment.length; } } #else const qreal factor = ( size - m_boundingHint.minimum() ) / ( m_boundingHint.preferred() - m_boundingHint.minimum() ); - for ( uint i = 0; i < m_cells.size(); i++ ) + for ( int i = 0; i < m_cells.count(); i++ ) { const auto& cell = m_cells[i]; - auto& range = ranges[i]; + auto& segment = segments[i]; if ( !cell.isValid ) { - range.start = offset; - range.length = 0.0; + segment.start = offset; + segment.length = 0.0; } else { offset += fillSpacing; fillSpacing = m_spacing; - range.start = offset; - range.length = cell.hint.minimum() + segment.start = offset; + segment.length = cell.hint.minimum() + factor * ( cell.hint.preferred() - cell.hint.minimum() ); - offset += range.length; + offset += segment.length; } } #endif - return ranges; + return segments; } -QVector< QskLayoutChain::Range > QskLayoutChain::preferredStretched( qreal size ) const +QskLayoutChain::Segments QskLayoutChain::preferredStretched( qreal size ) const { const int count = m_cells.size(); qreal sumFactors = 0.0; QVarLengthArray< qreal > factors( count ); - QVector< Range > ranges( count ); + Segments segments( count ); for ( int i = 0; i < count; i++ ) { @@ -315,7 +414,7 @@ QVector< QskLayoutChain::Range > QskLayoutChain::preferredStretched( qreal size if ( !cell.isValid ) { - ranges[i].length = 0.0; + segments[i].length = 0.0; factors[i] = -1.0; continue; } @@ -355,7 +454,7 @@ QVector< QskLayoutChain::Range > QskLayoutChain::preferredStretched( qreal size if ( boundedSize != size ) { - ranges[i].length = boundedSize; + segments[i].length = boundedSize; sumSizes -= boundedSize; sumFactors -= factors[i]; factors[i] = -1.0; @@ -374,7 +473,7 @@ QVector< QskLayoutChain::Range > QskLayoutChain::preferredStretched( qreal size for ( int i = 0; i < count; i++ ) { const auto& cell = m_cells[i]; - auto& range = ranges[i]; + auto& segment = segments[i]; const auto& factor = factors[i]; @@ -384,37 +483,37 @@ QVector< QskLayoutChain::Range > QskLayoutChain::preferredStretched( qreal size fillSpacing = m_spacing; } - range.start = offset; + segment.start = offset; if ( factor >= 0.0 ) { if ( factor > 0.0 ) - range.length = sumSizes * factor / sumFactors; + segment.length = sumSizes * factor / sumFactors; else - range.length = cell.hint.preferred(); + segment.length = cell.hint.preferred(); } - offset += range.length; + offset += segment.length; } - return ranges; + return segments; } #ifndef QT_NO_DEBUG_STREAM #include -QDebug operator<<( QDebug debug, const QskLayoutChain::Range& range ) +QDebug operator<<( QDebug debug, const QskLayoutChain::Segment& segment ) { QDebugStateSaver saver( debug ); debug.nospace(); - debug << "( " << range.start << ", " << range.end() << " )"; + debug << "( " << segment.start << ", " << segment.end() << " )"; return debug; } -QDebug operator<<( QDebug debug, const QskLayoutChain::Cell& cell ) +QDebug operator<<( QDebug debug, const QskLayoutChain::CellData& cell ) { QDebugStateSaver saver( debug ); debug.nospace(); diff --git a/src/layouts/QskLayoutChain.h b/src/layouts/QskLayoutChain.h index 17b18fe3..d267288f 100644 --- a/src/layouts/QskLayoutChain.h +++ b/src/layouts/QskLayoutChain.h @@ -7,16 +7,15 @@ #define QSK_LAYOUT_CHAIN_H #include -#include +#include #include -#include class QDebug; class QskLayoutChain { public: - class Range + class Segment { public: inline qreal end() const { return start + length; } @@ -25,21 +24,12 @@ class QskLayoutChain qreal length = 0.0; }; - class Cell + typedef QVector< Segment > Segments; + + class CellData { public: - Cell() - { - } - - Cell( QskLayoutHint hint, int stretch ) - : hint( hint ) - , stretch( stretch ) - , isValid( true ) - { - } - - inline bool operator==( const Cell& other ) const + inline bool operator==( const CellData& other ) const { return ( isValid == other.isValid ) && ( canGrow == other.canGrow ) @@ -47,65 +37,83 @@ class QskLayoutChain && ( hint == other.hint ); } - inline bool operator!=( const Cell& other ) const + inline bool operator!=( const CellData& other ) const { return !( *this == other ); } + inline qreal size( int which ) const + { + return hint.size( which ); + } + + inline void setSize( int which, qreal size ) + { + hint.setSize( which, size ); + } + QskLayoutHint hint; + int stretch = 0; bool canGrow = false; bool isValid = false; }; + enum ExtraSpacing + { + Leading = 1 << 0, + Trailing = 1 << 1 + }; + QskLayoutChain(); ~QskLayoutChain(); void invalidate(); void reset( int count, qreal constraint ); - void expandTo( int index, const Cell& ); + void expandCell( int index, const CellData& ); + void expandCells( int start, int end, const CellData& ); + void narrowCell( int index, const CellData& ); void finish(); - const Cell& cell( int index ) const { return m_cells[ index ]; } + const CellData& cell( int index ) const { return m_cells[ index ]; } bool setSpacing( qreal spacing ); qreal spacing() const { return m_spacing; } - void setExtraSpacingAt( Qt::Edges edges ) { m_extraSpacingAt = edges; } + void setExtraSpacingAt( int extraSpacingAt ) { m_extraSpacingAt = extraSpacingAt; } - QVector< Range > geometries( qreal size ) const; + Segments segments( qreal size ) const; QskLayoutHint boundingHint() const { return m_boundingHint; } inline qreal constraint() const { return m_constraint; } inline int count() const { return m_cells.size(); } private: - Q_DISABLE_COPY( QskLayoutChain ) - - QVector< Range > distributed( int which, qreal offset, qreal extra ) const; - QVector< Range > minimumExpanded( qreal size ) const; - QVector< Range > preferredStretched( qreal size ) const; + Segments distributed( int which, qreal offset, qreal extra ) const; + Segments minimumExpanded( qreal size ) const; + Segments preferredStretched( qreal size ) const; QskLayoutHint m_boundingHint; qreal m_constraint = -2.0; qreal m_spacing = 0; - Qt::Edges m_extraSpacingAt; + int m_extraSpacingAt; int m_sumStretches = 0; int m_validCells = 0; - std::vector< Cell > m_cells; + + QVector< CellData > m_cells; }; #ifndef QT_NO_DEBUG_STREAM -QDebug operator<<( QDebug, const QskLayoutChain::Range& ); -QDebug operator<<( QDebug, const QskLayoutChain::Cell& ); +QDebug operator<<( QDebug, const QskLayoutChain::Segment& ); +QDebug operator<<( QDebug, const QskLayoutChain::CellData& ); #endif -Q_DECLARE_TYPEINFO( QskLayoutChain::Range, Q_MOVABLE_TYPE ); -Q_DECLARE_TYPEINFO( QskLayoutChain::Cell, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( QskLayoutChain::Segment, Q_MOVABLE_TYPE ); +Q_DECLARE_TYPEINFO( QskLayoutChain::CellData, Q_MOVABLE_TYPE ); #endif diff --git a/src/layouts/QskLayoutEngine2D.cpp b/src/layouts/QskLayoutEngine2D.cpp new file mode 100644 index 00000000..edaf0008 --- /dev/null +++ b/src/layouts/QskLayoutEngine2D.cpp @@ -0,0 +1,455 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskLayoutEngine2D.h" +#include "QskLayoutChain.h" +#include "QskQuick.h" +#include + +namespace +{ + class LayoutData + { + public: + + QRectF geometryAt( const QRect& grid ) const + { + const auto x1 = columns[ grid.left() ].start; + const auto x2 = columns[ grid.right() ].end(); + const auto y1 = rows[ grid.top() ].start; + const auto y2 = rows[ grid.bottom() ].end(); + + return QRectF( rect.x() + x1, rect.y() + y1, x2 - x1, y2 - y1 ); + } + + Qt::LayoutDirection direction; + + QRectF rect; + QskLayoutChain::Segments rows; + QskLayoutChain::Segments columns; + }; +} + +class QskLayoutEngine2D::PrivateData +{ + public: + PrivateData() + : defaultAlignment( Qt::AlignLeft | Qt::AlignVCenter ) + , extraSpacingAt( 0 ) + , visualDirection( Qt::LeftToRight ) + , constraintType( -1 ) + , blockInvalidate( false ) + { + } + + inline QskLayoutChain& layoutChain( Qt::Orientation orientation ) + { + return ( orientation == Qt::Horizontal ) ? columnChain : rowChain; + } + + inline Qt::Alignment effectiveAlignment( Qt::Alignment alignment ) const + { + const auto align = static_cast< Qt::Alignment >( defaultAlignment ); + + if ( !( alignment & Qt::AlignVertical_Mask ) ) + alignment |= ( align & Qt::AlignVertical_Mask ); + + if ( !( alignment & Qt::AlignHorizontal_Mask ) ) + alignment |= ( align & Qt::AlignHorizontal_Mask ); + + return alignment; + } + + QskLayoutChain columnChain; + QskLayoutChain rowChain; + + QSizeF layoutSize; + + QskLayoutChain::Segments rows; + QskLayoutChain::Segments columns; + + const LayoutData* layoutData = nullptr; + + unsigned int defaultAlignment : 8; + unsigned int extraSpacingAt : 4; + unsigned int visualDirection : 4; + + int constraintType : 3; + + /* + Some weired controls do lazy updates inside of their sizeHint calculation + that lead to LayoutRequest events. While being in the process of + updating the tables we can't - and don't need to - handle invalidations + because of them. + */ + bool blockInvalidate : 1; +}; + +QskLayoutEngine2D::QskLayoutEngine2D() + : m_data( new PrivateData ) +{ + m_data->columnChain.setSpacing( defaultSpacing( Qt::Horizontal ) ); + m_data->rowChain.setSpacing( defaultSpacing( Qt::Vertical ) ); +} + +QskLayoutEngine2D::~QskLayoutEngine2D() +{ +} + +bool QskLayoutEngine2D::setVisualDirection( Qt::LayoutDirection direction ) +{ + if ( m_data->visualDirection != direction ) + { + m_data->visualDirection = direction; + return true; + } + + return false; +} + +Qt::LayoutDirection QskLayoutEngine2D::visualDirection() const +{ + return static_cast< Qt::LayoutDirection >( m_data->visualDirection ); +} + +bool QskLayoutEngine2D::setDefaultAlignment( Qt::Alignment alignment ) +{ + if ( defaultAlignment() != alignment ) + { + m_data->defaultAlignment = alignment; + return true; + } + + return false; +} + +Qt::Alignment QskLayoutEngine2D::defaultAlignment() const +{ + return static_cast< Qt::Alignment >( m_data->defaultAlignment ); +} + + +qreal QskLayoutEngine2D::defaultSpacing( Qt::Orientation ) const +{ + return 5.0; // should be from the skin +} + +bool QskLayoutEngine2D::setSpacing( + qreal spacing, Qt::Orientations orientations ) +{ + if ( spacing < 0.0 ) + spacing = 0.0; + + bool isModified = false; + + for ( auto o : { Qt::Horizontal, Qt::Vertical } ) + { + if ( orientations & o ) + isModified |= m_data->layoutChain( o ).setSpacing( spacing ); + } + + if ( isModified ) + invalidate( LayoutCache ); + + return isModified; +} + +qreal QskLayoutEngine2D::spacing( Qt::Orientation orientation ) const +{ + return m_data->layoutChain( orientation ).spacing(); +} + +bool QskLayoutEngine2D::setExtraSpacingAt( Qt::Edges edges ) +{ + if ( edges == extraSpacingAt() ) + return false; + + m_data->extraSpacingAt = edges; + + int value = 0; + + if ( edges & Qt::LeftEdge ) + value |= QskLayoutChain::Leading; + + if ( edges & Qt::RightEdge ) + value |= QskLayoutChain::Trailing; + + m_data->columnChain.setExtraSpacingAt( value ); + + value = 0; + + if ( edges & Qt::TopEdge ) + value |= QskLayoutChain::Leading; + + if ( edges & Qt::BottomEdge ) + value |= QskLayoutChain::Trailing; + + m_data->rowChain.setExtraSpacingAt( value ); + + invalidate(); + + return true; +} + +int QskLayoutEngine2D::indexOf( const QQuickItem* item ) const +{ + if ( item ) + { + /* + indexOf is often called after inserting an item to + set additinal properties. So we search in reverse order + */ + + for ( int i = count() - 1; i >= 0; --i ) + { + if ( itemAt( i ) == item ) + return i; + } + } + + return -1; +} + +Qt::Edges QskLayoutEngine2D::extraSpacingAt() const +{ + return static_cast< Qt::Edges >( m_data->extraSpacingAt ); +} + +void QskLayoutEngine2D::setGeometries( const QRectF& rect ) +{ + if ( rowCount() < 1 || columnCount() < 1 ) + return; + + if ( m_data->layoutSize != rect.size() ) + { + m_data->layoutSize = rect.size(); + updateSegments( rect.size() ); + } + + /* + In case we have items that send LayoutRequest events on + geometry changes - what doesn't make much sense - we + better make a ( implicitely shared ) copy of the rows/columns. + */ + LayoutData data; + data.rows = m_data->rows; + data.columns = m_data->columns; + data.rect = rect; + + data.direction = visualDirection(); + if ( data.direction == Qt::LayoutDirectionAuto ) + data.direction = QGuiApplication::layoutDirection(); + + m_data->layoutData = &data; + layoutItems(); + m_data->layoutData = nullptr; +} + +void QskLayoutEngine2D::layoutItem( QQuickItem* item, + const QRect& grid, Qt::Alignment alignment ) const +{ + auto layoutData = m_data->layoutData; + + if ( layoutData == nullptr || item == nullptr ) + return; + + alignment = m_data->effectiveAlignment( alignment ); + + QRectF rect = layoutData->geometryAt( grid ); + rect = QskLayoutConstraint::itemRect(item, rect, alignment ); + + if ( layoutData->direction == Qt::RightToLeft ) + { + const auto& r = layoutData->rect; + rect.moveRight( r.right() - ( rect.left() - r.left() ) ); + } + + qskSetItemGeometry( item, rect ); +} + +qreal QskLayoutEngine2D::widthForHeight( qreal height ) const +{ + const QSizeF constraint( -1, height ); + return sizeHint( Qt::PreferredSize, constraint ).width(); +} + +qreal QskLayoutEngine2D::heightForWidth( qreal width ) const +{ + const QSizeF constraint( width, -1 ); + return sizeHint( Qt::PreferredSize, constraint ).height(); +} + +QSizeF QskLayoutEngine2D::sizeHint( + Qt::SizeHint which, const QSizeF& constraint ) const +{ + if ( effectiveCount( Qt::Horizontal ) <= 0 ) + return QSizeF( 0.0, 0.0 ); + + auto& rowChain = m_data->rowChain; + auto& columnChain = m_data->columnChain; + + m_data->blockInvalidate = true; + + if ( ( constraint.width() >= 0 ) && + ( constraintType() == QskLayoutConstraint::HeightForWidth ) ) + { + setupChain( Qt::Horizontal ); + + const auto constraints = columnChain.segments( constraint.width() ); + setupChain( Qt::Vertical, constraints ); + } + else if ( ( constraint.height() >= 0 ) && + ( constraintType() == QskLayoutConstraint::WidthForHeight ) ) + { + setupChain( Qt::Vertical ); + + const auto constraints = rowChain.segments( constraint.height() ); + setupChain( Qt::Horizontal, constraints ); + } + else + { + setupChain( Qt::Horizontal ); + setupChain( Qt::Vertical ); + } + + m_data->blockInvalidate = false; + + const qreal width = columnChain.boundingHint().size( which ); + const qreal height = rowChain.boundingHint().size( which ); + + return QSizeF( width, height ); +} + +void QskLayoutEngine2D::setupChain( Qt::Orientation orientation ) const +{ + setupChain( orientation, QskLayoutChain::Segments() ); +} + +void QskLayoutEngine2D::setupChain( Qt::Orientation orientation, + const QskLayoutChain::Segments& constraints ) const +{ + const auto count = effectiveCount( orientation ); + const qreal constraint = + constraints.isEmpty() ? -1.0 : constraints.last().end(); + + auto& chain = m_data->layoutChain( orientation ); + + if ( ( chain.constraint() == constraint ) + && ( chain.count() == count ) ) + { + return; // already up to date + } + + chain.reset( count, constraint ); + setupChain( orientation, constraints, chain ); + chain.finish(); + +#if 0 + qDebug() << "==" << this << orientation << chain.count(); + + for ( int i = 0; i < chain.count(); i++ ) + qDebug() << i << ":" << chain.cell( i ); +#endif +} + +void QskLayoutEngine2D::updateSegments( const QSizeF& size ) const +{ + auto& rowChain = m_data->rowChain; + auto& colLine = m_data->columnChain; + + auto& rows = m_data->rows; + auto& columns = m_data->columns; + + m_data->blockInvalidate = true; + + switch( constraintType() ) + { + case QskLayoutConstraint::WidthForHeight: + { + setupChain( Qt::Vertical ); + rows = rowChain.segments( size.height() ); + + setupChain( Qt::Horizontal, rows ); + columns = colLine.segments( size.width() ); + + break; + } + case QskLayoutConstraint::HeightForWidth: + { + setupChain( Qt::Horizontal ); + columns = colLine.segments( size.width() ); + + setupChain( Qt::Vertical, m_data->columns ); + rows = rowChain.segments( size.height() ); + + break; + } + default: + { + setupChain( Qt::Horizontal ); + columns = colLine.segments( size.width() ); + + setupChain( Qt::Vertical ); + rows = rowChain.segments( size.height() ); + } + } + + m_data->blockInvalidate = false; +} + +void QskLayoutEngine2D::invalidate( int what ) +{ + if ( m_data->blockInvalidate ) + return; + + if ( what & ElementCache ) + { + m_data->constraintType = -1; + invalidateElementCache(); + } + + if ( what & LayoutCache ) + { + m_data->rowChain.invalidate(); + m_data->columnChain.invalidate(); + + m_data->layoutSize = QSize(); + m_data->rows.clear(); + m_data->columns.clear(); + } +} + +QskLayoutConstraint::Type QskLayoutEngine2D::constraintType() const +{ + if ( m_data->constraintType < 0 ) + { + auto constraintType = QskLayoutConstraint::Unconstrained; + + for ( int i = 0; i < count(); i++ ) + { + const auto type = QskLayoutConstraint::constraintType( itemAt( i ) ); + + using namespace QskLayoutConstraint; + + if ( type != Unconstrained ) + { + if ( constraintType == Unconstrained ) + { + constraintType = type; + } + else if ( constraintType != type ) + { + qWarning( "QskLayoutEngine2D: conflicting constraints"); + constraintType = Unconstrained; + } + } + } + + m_data->constraintType = constraintType; + } + + return static_cast< QskLayoutConstraint::Type >( m_data->constraintType ); +} + diff --git a/src/layouts/QskLayoutEngine2D.h b/src/layouts/QskLayoutEngine2D.h new file mode 100644 index 00000000..6cb07273 --- /dev/null +++ b/src/layouts/QskLayoutEngine2D.h @@ -0,0 +1,104 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_LAYOUT_ENGINE_2D_H +#define QSK_LAYOUT_ENGINE_2D_H + +#include "QskGlobal.h" +#include "QskLayoutChain.h" +#include "QskLayoutConstraint.h" + +#include +#include + +class QskLayoutEngine2D +{ + public: + QskLayoutEngine2D(); + virtual ~QskLayoutEngine2D(); + + virtual int count() const = 0; + + virtual QQuickItem* itemAt( int index ) const = 0; + virtual qreal spacerAt( int index ) const = 0; + + int indexOf( const QQuickItem* ) const; + + int rowCount() const; + int columnCount() const; + + bool setVisualDirection( Qt::LayoutDirection ); + Qt::LayoutDirection visualDirection() const; + + bool setDefaultAlignment( Qt::Alignment ); + Qt::Alignment defaultAlignment() const; + + bool setExtraSpacingAt( Qt::Edges edges ); + Qt::Edges extraSpacingAt() const; + + bool setSpacing( qreal spacing, Qt::Orientations ); + qreal spacing( Qt::Orientation ) const; + + qreal defaultSpacing( Qt::Orientation ) const; + + void invalidate(); + + qreal widthForHeight( qreal height ) const; + qreal heightForWidth( qreal width ) const; + + QSizeF sizeHint( Qt::SizeHint, const QSizeF& constraint ) const; + + void setGeometries( const QRectF& ); + + protected: + + void layoutItem( QQuickItem*, + const QRect& grid, Qt::Alignment ) const; + + enum + { + ElementCache = 1 << 0, + LayoutCache = 1 << 1 + }; + + void invalidate( int what ); + + private: + Q_DISABLE_COPY( QskLayoutEngine2D ) + + void updateSegments( const QSizeF& ) const; + + virtual void layoutItems() = 0; + virtual int effectiveCount( Qt::Orientation ) const = 0; + + virtual void invalidateElementCache() = 0; + QskLayoutConstraint::Type constraintType() const; + + void setupChain( Qt::Orientation ) const; + void setupChain( Qt::Orientation, const QskLayoutChain::Segments& ) const; + + virtual void setupChain( Qt::Orientation, + const QskLayoutChain::Segments&, QskLayoutChain& ) const = 0; + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +inline void QskLayoutEngine2D::invalidate() +{ + invalidate( ElementCache | LayoutCache ); +} + +inline int QskLayoutEngine2D::rowCount() const +{ + return effectiveCount( Qt::Vertical ); +} + +inline int QskLayoutEngine2D::columnCount() const +{ + return effectiveCount( Qt::Horizontal ); +} + +#endif diff --git a/src/layouts/QskLayoutHint.cpp b/src/layouts/QskLayoutHint.cpp index ccc118a0..ee386e54 100644 --- a/src/layouts/QskLayoutHint.cpp +++ b/src/layouts/QskLayoutHint.cpp @@ -58,15 +58,6 @@ void QskLayoutHint::setSize( int which, qreal size ) } } -void QskLayoutHint::expandTo( const QskLayoutHint& other ) -{ - const auto hint = other.normalized(); - - m_minimum = qMax( m_minimum, hint.m_minimum ); - m_preferred = qMax( m_preferred, hint.m_preferred ); - m_maximum = qMax( m_maximum, hint.m_maximum ); -} - void QskLayoutHint::normalize() { m_minimum = qMax( m_minimum, qreal( 0.0 ) ); diff --git a/src/layouts/QskLayoutHint.h b/src/layouts/QskLayoutHint.h index f1af1f45..3551a66d 100644 --- a/src/layouts/QskLayoutHint.h +++ b/src/layouts/QskLayoutHint.h @@ -17,7 +17,6 @@ class QSK_EXPORT QskLayoutHint QskLayoutHint(); QskLayoutHint( qreal minimum, qreal preferred, qreal maximum ); - void expandTo( const QskLayoutHint& ); void normalize(); QskLayoutHint normalized() const; @@ -38,6 +37,8 @@ class QSK_EXPORT QskLayoutHint void setMaximum( qreal value ); qreal maximum() const; + void setSizes( qreal minimum, qreal preferred, qreal maximum ); + private: qreal m_minimum; qreal m_preferred; @@ -74,6 +75,14 @@ inline void QskLayoutHint::setMaximum( qreal value ) m_maximum = value; } +inline void QskLayoutHint::setSizes( + qreal minimum, qreal preferred, qreal maximum ) +{ + m_minimum = minimum; + m_preferred = preferred; + m_maximum = maximum; +} + inline bool QskLayoutHint::operator==( const QskLayoutHint& other ) const { return ( m_preferred == other.m_preferred ) diff --git a/src/layouts/QskLinearBox.cpp b/src/layouts/QskLinearBox.cpp index 26293e9d..8ea9b8fc 100644 --- a/src/layouts/QskLinearBox.cpp +++ b/src/layouts/QskLinearBox.cpp @@ -94,26 +94,7 @@ QQuickItem* QskLinearBox::itemAtIndex( int index ) const int QskLinearBox::indexOf( const QQuickItem* item ) const { - if ( item ) - { - /* - Linear search might become slow for many items, - better introduce some sort of hash table TODO ... - - indexOf is often used for configuring an item - after inserting it. So we iterate in reverse order - */ - - const auto& engine = m_data->engine; - - for ( int i = engine.count() - 1; i >= 0; --i ) - { - if ( engine.itemAt( i ) == item ) - return i; - } - } - - return -1; + return m_data->engine.indexOf( item ); } void QskLinearBox::removeAt( int index ) @@ -398,16 +379,16 @@ Qt::Edges QskLinearBox::extraSpacingAt() const return m_data->engine.extraSpacingAt(); } -void QskLinearBox::addItem( QQuickItem* item, Qt::Alignment alignment ) +int QskLinearBox::addItem( QQuickItem* item, Qt::Alignment alignment ) { - insertItem( -1, item, alignment ); + return insertItem( -1, item, alignment ); } -void QskLinearBox::insertItem( +int QskLinearBox::insertItem( int index, QQuickItem* item, Qt::Alignment alignment ) { if ( item == nullptr ) - return; + return -1; auto& engine = m_data->engine; @@ -424,7 +405,7 @@ void QskLinearBox::insertItem( ( doAppend && oldIndex == engine.count() - 1 ) ) { // already at its position, nothing to do - return; + return oldIndex; } removeAt( oldIndex ); @@ -433,11 +414,7 @@ void QskLinearBox::insertItem( reparentItem( item ); - const int numItems = engine.count(); - if ( index < 0 || index > numItems ) - index = numItems; - - engine.insertItem( item, index ); + index = engine.insertItem( item, index ); engine.setAlignmentAt( index, alignment ); // Re-ordering the child items to have a a proper focus tab chain @@ -472,14 +449,16 @@ void QskLinearBox::insertItem( resetImplicitSize(); polish(); #endif + + return index; } -void QskLinearBox::addSpacer( qreal spacing, int stretchFactor ) +int QskLinearBox::addSpacer( qreal spacing, int stretchFactor ) { - insertSpacer( -1, spacing, stretchFactor ); + return insertSpacer( -1, spacing, stretchFactor ); } -void QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor ) +int QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor ) { auto& engine = m_data->engine; @@ -487,7 +466,7 @@ void QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor ) if ( index < 0 || index > numItems ) index = numItems; - engine.insertSpacerAt( index, spacing ); + index = engine.insertSpacerAt( index, spacing ); stretchFactor = qMax( stretchFactor, 0 ); engine.setStretchFactorAt( index, stretchFactor ); @@ -497,16 +476,18 @@ void QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor ) resetImplicitSize(); polish(); #endif + + return index; } -void QskLinearBox::addStretch( int stretchFactor ) +int QskLinearBox::addStretch( int stretchFactor ) { - insertSpacer( -1, 0, stretchFactor ); + return insertSpacer( -1, 0, stretchFactor ); } -void QskLinearBox::insertStretch( int index, int stretchFactor ) +int QskLinearBox::insertStretch( int index, int stretchFactor ) { - insertSpacer( index, 0, stretchFactor ); + return insertSpacer( index, 0, stretchFactor ); } void QskLinearBox::setAlignment( int index, Qt::Alignment alignment ) diff --git a/src/layouts/QskLinearBox.h b/src/layouts/QskLinearBox.h index b0f5c37a..3a6bd5b4 100644 --- a/src/layouts/QskLinearBox.h +++ b/src/layouts/QskLinearBox.h @@ -75,17 +75,17 @@ class QSK_EXPORT QskLinearBox : public QskIndexedLayoutBox void resetSpacing(); qreal spacing() const; - Q_INVOKABLE void addItem( + Q_INVOKABLE int addItem( QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); - Q_INVOKABLE void insertItem( + Q_INVOKABLE int insertItem( int index, QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); - Q_INVOKABLE void addSpacer( qreal spacing, int stretchFactor = 0 ); - Q_INVOKABLE void insertSpacer( int index, qreal spacing, int stretchFactor = 0 ); + Q_INVOKABLE int addSpacer( qreal spacing, int stretchFactor = 0 ); + Q_INVOKABLE int insertSpacer( int index, qreal spacing, int stretchFactor = 0 ); - Q_INVOKABLE void addStretch( int stretchFactor = 0 ); - Q_INVOKABLE void insertStretch( int index, int stretchFactor = 0 ); + Q_INVOKABLE int addStretch( int stretchFactor = 0 ); + Q_INVOKABLE int insertStretch( int index, int stretchFactor = 0 ); Q_INVOKABLE void setStretchFactor( int index, int stretchFactor ); Q_INVOKABLE int stretchFactor( int index ) const; diff --git a/src/layouts/QskLinearLayoutEngine.cpp b/src/layouts/QskLinearLayoutEngine.cpp index 72fd6e35..3fc40ac6 100644 --- a/src/layouts/QskLinearLayoutEngine.cpp +++ b/src/layouts/QskLinearLayoutEngine.cpp @@ -12,59 +12,32 @@ #include -static constexpr qreal qskDefaultSpacing() -{ - // should be a skin hint - return 5.0; -} - namespace { - class CellGeometries + class Element { public: - void invalidate() - { - boundingSize = QSizeF(); - rows.clear(); - columns.clear(); - } + Element( QQuickItem* item ); + Element( qreal spacing ); - QRectF geometryAt( int row, int col ) const - { - return QRectF( - columns[ col ].start, rows[ row ].start, - columns[ col ].length, rows[ row ].length ); - } + Element& operator=( const Element& ); - QSizeF boundingSize; + qreal spacer() const; + QQuickItem* item() const; - QVector< QskLayoutChain::Range > rows; - QVector< QskLayoutChain::Range > columns; - }; + Qt::Alignment alignment() const; + void setAlignment( Qt::Alignment ); - class EntryData - { - public: - EntryData( QQuickItem* item ); - EntryData( qreal spacing ); + bool retainSizeWhenHidden() const; + void setRetainSizeWhenHidden( bool ); - EntryData& operator=( const EntryData& ); + int stretch() const; + void setStretch( int ); bool isIgnored() const; - qreal spacer() const { return m_isSpacer ? m_spacer : -1.0; } - QQuickItem* item() const { return m_isSpacer ? nullptr : m_item; } - - inline Qt::Alignment alignment() const - { return static_cast< Qt::Alignment >( m_alignment ); } - inline void setAlignment( Qt::Alignment alignment ) { m_alignment = alignment; } - - inline int stretch() const { return m_stretch; } - inline void setStretch( int stretch ) { m_stretch = stretch; } - - bool retainSizeWhenHidden() const { return m_retainSizeWhenHidden; } - void setRetainSizeWhenHidden( bool on ) { m_retainSizeWhenHidden = on; } + QskLayoutChain::CellData cell( Qt::Orientation, + bool isLayoutOrientation, qreal constraint ) const; private: @@ -80,84 +53,9 @@ namespace bool m_isSpacer : 1; bool m_retainSizeWhenHidden : 1; }; - - class EntryTable - { - public: - EntryTable( Qt::Orientation, uint dimension ); - - bool setOrientation( Qt::Orientation orientation ); - Qt::Orientation orientation() const; - - bool setDimension( uint dimension ); - uint dimension() const; - - bool setDefaultAlignment( Qt::Alignment ); - Qt::Alignment defaultAlignment() const; - - Qt::Alignment effectiveAlignmentAt( int index ) const; - - void insertItem( int index, QQuickItem* ); - void insertSpacer( int index, qreal spacing ); - - QQuickItem* itemAt( int index ) const; - int spacerAt( int index ) const; - - bool removeAt( int index ); - bool clear(); - - bool isIgnoredAt( int index ) const; - - bool setAlignmentAt( int index, Qt::Alignment ); - Qt::Alignment alignmentAt( int index ) const; - - bool setStretchFactorAt( int index, int stretchFactor ); - int stretchFactorAt( int index ) const; - - bool setRetainSizeWhenHiddenAt( int index, bool on ); - bool retainSizeWhenHiddenAt( int index ) const; - - inline int count() const { return m_entries.size(); } - - int effectiveCount() const; - int effectiveCount( Qt::Orientation orientation ) const; - - void updateLayoutChain( Qt::Orientation, - const QVector< QskLayoutChain::Range >& constraints, - QskLayoutChain& ) const; - - QskLayoutConstraint::Type constraintType() const; - - void invalidate(); - - private: - QskLayoutChain::Cell cell( const EntryData&, - Qt::Orientation, qreal constraint ) const; - - inline EntryData* entryAt( int index ) const - { - if ( index < 0 || index >= count() ) - { - // qWarning, TODO ... - return nullptr; - } - - return const_cast< EntryData* >( &m_entries[index] ); - } - - std::vector< EntryData > m_entries; - - uint m_dimension; - mutable int m_sumIgnored : 19; - mutable int m_constraintType : 3; - - unsigned int m_defaultAlignment : 8; - unsigned int m_orientation : 2; - }; - } -EntryData::EntryData( QQuickItem* item ) +Element::Element( QQuickItem* item ) : m_item( item ) , m_stretch( -1 ) , m_alignment( 0 ) @@ -166,7 +64,7 @@ EntryData::EntryData( QQuickItem* item ) { } -EntryData::EntryData( qreal spacing ) +Element::Element( qreal spacing ) : m_spacer( spacing ) , m_stretch( -1 ) , m_alignment( 0 ) @@ -175,7 +73,7 @@ EntryData::EntryData( qreal spacing ) { } -EntryData& EntryData::operator=( const EntryData& other ) +Element& Element::operator=( const Element& other ) { m_isSpacer = other.m_isSpacer; @@ -191,7 +89,47 @@ EntryData& EntryData::operator=( const EntryData& other ) return *this; } -bool EntryData::isIgnored() const +inline qreal Element::spacer() const +{ + return m_isSpacer ? m_spacer : -1.0; +} + +inline QQuickItem* Element::item() const +{ + return m_isSpacer ? nullptr : m_item; +} + +inline Qt::Alignment Element::alignment() const +{ + return static_cast< Qt::Alignment >( m_alignment ); +} + +inline void Element::setAlignment( Qt::Alignment alignment ) +{ + m_alignment = alignment; +} + +bool Element::retainSizeWhenHidden() const +{ + return m_retainSizeWhenHidden; +} + +void Element::setRetainSizeWhenHidden( bool on ) +{ + m_retainSizeWhenHidden = on; +} + +inline int Element::stretch() const +{ + return m_stretch; +} + +inline void Element::setStretch( int stretch ) +{ + m_stretch = stretch; +} + +bool Element::isIgnored() const { if ( !m_isSpacer && !m_retainSizeWhenHidden ) return !qskIsVisibleToParent( m_item ); @@ -199,438 +137,76 @@ bool EntryData::isIgnored() const return false; } -EntryTable::EntryTable( Qt::Orientation orientation, uint dimension ) - : m_dimension( dimension ) - , m_sumIgnored( -1 ) - , m_constraintType( -1 ) - , m_defaultAlignment( Qt::AlignLeft | Qt::AlignVCenter ) - , m_orientation( orientation ) +QskLayoutChain::CellData Element::cell( Qt::Orientation orientation, + bool isLayoutOrientation, qreal constraint ) const { -} - -bool EntryTable::setOrientation( Qt::Orientation orientation ) -{ - if ( m_orientation != orientation ) - { - m_orientation = orientation; - return true; - } - - return false; -} - -Qt::Orientation EntryTable::orientation() const -{ - return static_cast< Qt::Orientation >( m_orientation ); -} - -bool EntryTable::setDimension( uint dimension ) -{ - if ( m_dimension != dimension ) - { - m_dimension = dimension; - return true; - } - - return false; -} - -uint EntryTable::dimension() const -{ - return m_dimension; -} - -bool EntryTable::setDefaultAlignment( Qt::Alignment alignment ) -{ - if ( defaultAlignment() != alignment ) - { - m_defaultAlignment = alignment; - return true; - } - - return false; -} - -Qt::Alignment EntryTable::defaultAlignment() const -{ - return static_cast< Qt::Alignment >( m_defaultAlignment ); -} - -Qt::Alignment EntryTable::effectiveAlignmentAt( int index ) const -{ - Qt::Alignment alignment; - - if ( const auto entry = entryAt( index ) ) - alignment = entry->alignment(); - - if ( !( alignment & Qt::AlignVertical_Mask ) ) - alignment |= ( defaultAlignment() & Qt::AlignVertical_Mask ); - - if ( !( alignment & Qt::AlignHorizontal_Mask ) ) - alignment |= ( defaultAlignment() & Qt::AlignHorizontal_Mask ); - - return alignment; -} - -bool EntryTable::isIgnoredAt( int index ) const -{ - if ( index >= 0 && index < count() ) - return m_entries[index].isIgnored(); - - return false; -} - -void EntryTable::insertItem( int index, QQuickItem* item ) -{ - if ( index < 0 || index > count() ) - m_entries.emplace_back( item ); - else - m_entries.emplace( m_entries.begin() + index, item ); - - invalidate(); -} - -void EntryTable::insertSpacer( int index, qreal spacing ) -{ - spacing = qMax( spacing, static_cast< qreal >( 0.0 ) ); - - if ( index < 0 || index > count() ) - m_entries.emplace_back( spacing ); - else - m_entries.emplace( m_entries.begin() + index, spacing ); -} - -bool EntryTable::removeAt( int index ) -{ - auto entry = entryAt( index ); - if ( entry == nullptr ) - return false; - - if ( entry->isIgnored() ) - m_sumIgnored--; - - const auto itemType = QskLayoutConstraint::constraintType( entry->item() ); - if ( itemType > QskLayoutConstraint::Unconstrained ) - m_constraintType = -1; - - m_entries.erase( m_entries.begin() + index ); - - return true; -} - -bool EntryTable::clear() -{ - if ( count() > 0 ) - { - m_entries.clear(); - invalidate(); - - return true; - } - - return false; -} - -QQuickItem* EntryTable::itemAt( int index ) const -{ - if ( const auto entry = entryAt( index ) ) - return entry->item(); - - return nullptr; -} - -int EntryTable::spacerAt( int index ) const -{ - if ( const auto entry = entryAt( index ) ) - return entry->spacer(); - - return -1; -} - -bool EntryTable::setAlignmentAt( int index, Qt::Alignment alignment ) -{ - if ( auto entry = entryAt( index ) ) - { - if ( alignment != entry->alignment() ) - entry->setAlignment( alignment ); - - return true; - } - - return false; -} - -Qt::Alignment EntryTable::alignmentAt( int index ) const -{ - if ( const auto entry = entryAt( index ) ) - return entry->alignment(); - - return Qt::Alignment(); -} - -bool EntryTable::setStretchFactorAt( int index, int stretchFactor ) -{ - if ( auto entry = entryAt( index ) ) - { - if ( stretchFactor < 0 ) - stretchFactor = -1; - - if ( entry->stretch() != stretchFactor ) - { - entry->setStretch( stretchFactor ); - return true; - } - } - - return false; -} - -int EntryTable::stretchFactorAt( int index ) const -{ - if ( const auto entry = entryAt( index ) ) - return entry->stretch(); - - return -1; -} - -bool EntryTable::setRetainSizeWhenHiddenAt( int index, bool on ) -{ - if ( auto entry = entryAt( index ) ) - { - if ( on != entry->retainSizeWhenHidden() ) - { - const bool isIgnored = entry->isIgnored(); - - entry->setRetainSizeWhenHidden( on ); - - if ( isIgnored != entry->isIgnored() ) - { - if ( m_sumIgnored >= 0 ) - m_sumIgnored += on ? 1 : -1; - - return true; - } - } - } - - return false; -} - -bool EntryTable::retainSizeWhenHiddenAt( int index ) const -{ - if ( const auto entry = entryAt( index ) ) - return entry->retainSizeWhenHidden(); - - return false; -} - -void EntryTable::invalidate() -{ - m_sumIgnored = -1; - m_constraintType = -1; -} - -int EntryTable::effectiveCount() const -{ - if ( m_sumIgnored < 0 ) - { - m_sumIgnored = 0; - - for ( const auto& entry : m_entries ) - { - if ( entry.isIgnored() ) - m_sumIgnored++; - } - } - - return m_entries.size() - m_sumIgnored; -} - -int EntryTable::effectiveCount( Qt::Orientation orientation ) const -{ - const uint cellCount = effectiveCount(); - - if ( orientation == m_orientation ) - { - return qMin( cellCount, m_dimension ); - } - else - { - int count = cellCount / m_dimension; - if ( cellCount % m_dimension ) - count++; - - return count; - } -} - -QskLayoutConstraint::Type EntryTable::constraintType() const -{ - if ( m_constraintType < 0 ) - { - m_constraintType = QskLayoutConstraint::Unconstrained; - - for ( const auto& entry : m_entries ) - { - const auto itemType = QskLayoutConstraint::constraintType( entry.item() ); - - if ( itemType != QskLayoutConstraint::Unconstrained ) - { - if ( m_constraintType == QskLayoutConstraint::Unconstrained ) - { - m_constraintType = itemType; - } - else if ( m_constraintType != itemType ) - { - qWarning( "QskLinearLayoutEngine: conflicting constraints"); - m_constraintType = QskLayoutConstraint::Unconstrained; - } - } - } - } - - return static_cast< QskLayoutConstraint::Type >( m_constraintType ); -} - -QskLayoutChain::Cell EntryTable::cell( const EntryData& entry, - Qt::Orientation orientation, qreal constraint ) const -{ - QskLayoutChain::Cell cell; + QskLayoutChain::CellData cell; cell.canGrow = true; cell.isValid = true; - if ( const auto item = entry.item() ) + if ( !m_isSpacer ) { - cell.hint = QskLayoutConstraint::layoutHint( item, orientation, constraint ); + cell.hint = QskLayoutConstraint::layoutHint( m_item, orientation, constraint ); - const auto policy = QskLayoutConstraint::sizePolicy( item ).policy( orientation ); + const auto policy = QskLayoutConstraint::sizePolicy( m_item ).policy( orientation ); - if ( orientation == m_orientation ) + if ( isLayoutOrientation ) { - if ( entry.stretch() < 0 ) + if ( m_stretch < 0 ) cell.stretch = ( policy & QskSizePolicy::ExpandFlag ) ? 1 : 0; else - cell.stretch = entry.stretch(); + cell.stretch = m_stretch; } cell.canGrow = policy & QskSizePolicy::GrowFlag; } else { - if ( orientation == m_orientation ) + if ( isLayoutOrientation ) { - cell.hint.setMinimum( entry.spacer() ); - cell.hint.setPreferred( entry.spacer() ); + cell.hint.setMinimum( m_spacer ); + cell.hint.setPreferred( m_spacer ); - if ( entry.stretch() <= 0 ) - cell.hint.setMaximum( entry.spacer() ); + if ( m_stretch <= 0 ) + cell.hint.setMaximum( m_spacer ); - cell.stretch = qMax( entry.stretch(), 0 ); + cell.stretch = qMax( m_stretch, 0 ); } } return cell; } -void EntryTable::updateLayoutChain( Qt::Orientation orientation, - const QVector< QskLayoutChain::Range >& constraints, - QskLayoutChain& chain ) const -{ - const auto count = effectiveCount( orientation ); - const qreal constraint = - constraints.isEmpty() ? -1.0 : constraints.last().end(); - - if ( ( chain.constraint() == constraint ) - && ( chain.count() == count ) ) - { - return; // already up to date - } - - chain.reset( count, constraint ); - - uint index1 = 0; - uint index2 = 0; - - for ( const auto& entry : m_entries ) - { - if ( entry.isIgnored() ) - continue; - - const qreal cellConstraint = - constraints.isEmpty() ? -1.0 : constraints[index1].length; - - const auto cell = this->cell( entry, orientation, cellConstraint ); - chain.expandTo( index2, cell ); - - if ( m_orientation != orientation ) - { - if ( ++index1 == m_dimension ) - { - index1 = 0; - index2++; - } - } - else - { - if ( ++index2 == m_dimension ) - { - index2 = 0; - index1++; - } - } - } - - chain.finish(); -} - -// --------- - -static inline void qskUpdateLayoutChain( Qt::Orientation orientation, - const QVector< QskLayoutChain::Range >& constraints, - const EntryTable& entryTable, QskLayoutChain& chain ) -{ - entryTable.updateLayoutChain( orientation, constraints, chain ); -} - -static inline void qskUpdateLayoutChain( Qt::Orientation orientation, - const EntryTable& entryTable, QskLayoutChain& chain ) -{ - const QVector< QskLayoutChain::Range > constraints; - entryTable.updateLayoutChain( orientation, constraints, chain ); -} - class QskLinearLayoutEngine::PrivateData { public: PrivateData( Qt::Orientation orientation, uint dimension ) - : entryTable( orientation, dimension ) - , blockInvalidate( false ) + : dimension( dimension ) + , sumIgnored( -1 ) + , orientation( orientation ) { - rowChain.setSpacing( qskDefaultSpacing() ); - colChain.setSpacing( qskDefaultSpacing() ); } - EntryTable entryTable; + inline Element* elementAt( int index ) const + { + const int count = this->elements.size(); + if ( ( index < 0 ) || ( index >= count ) ) + return nullptr; - QskLayoutChain colChain; - QskLayoutChain rowChain; + return const_cast< Element* >( &this->elements[index] ); + } - CellGeometries geometries; + std::vector< Element > elements; - Qt::LayoutDirection visualDirection = Qt::LeftToRight; - Qt::Edges extraSpacingAt; + uint dimension; - /* - Some weired controls do lazy updates inside of their sizeHint calculation - that lead to LayoutRequest events. While being in the process of - updating the tables we can't - and don't need to - handle invalidations - because of them. - */ - bool blockInvalidate : 1; + mutable int sumIgnored : 30; + unsigned int orientation : 2; }; -QskLinearLayoutEngine::QskLinearLayoutEngine( Qt::Orientation orientation, uint dimension ) +QskLinearLayoutEngine::QskLinearLayoutEngine( + Qt::Orientation orientation, uint dimension ) : m_data( new PrivateData( orientation, dimension ) ) { } @@ -641,16 +217,20 @@ QskLinearLayoutEngine::~QskLinearLayoutEngine() bool QskLinearLayoutEngine::setOrientation( Qt::Orientation orientation ) { - const bool isModified = m_data->entryTable.setOrientation( orientation ); - if ( isModified ) - invalidate( CellCache | LayoutCache ); + if ( m_data->orientation != orientation ) + { + m_data->orientation = orientation; + invalidate( LayoutCache ); - return isModified; + return true; + } + + return false; } Qt::Orientation QskLinearLayoutEngine::orientation() const { - return m_data->entryTable.orientation(); + return static_cast< Qt::Orientation >( m_data->orientation ); } bool QskLinearLayoutEngine::setDimension( uint dimension ) @@ -658,257 +238,214 @@ bool QskLinearLayoutEngine::setDimension( uint dimension ) if ( dimension < 1 ) dimension = 1; - const bool isModified = - m_data->entryTable.setDimension( dimension ); + if ( m_data->dimension != dimension ) + { + m_data->dimension = dimension; + invalidate( LayoutCache ); - if ( isModified ) - invalidate( CellCache | LayoutCache ); + return true; + } - return isModified; + return false; } uint QskLinearLayoutEngine::dimension() const { - return m_data->entryTable.dimension(); + return m_data->dimension; } int QskLinearLayoutEngine::count() const { - return m_data->entryTable.count(); -} - -int QskLinearLayoutEngine::rowCount() const -{ - return m_data->entryTable.effectiveCount( Qt::Vertical ); -} - -int QskLinearLayoutEngine::columnCount() const -{ - return m_data->entryTable.effectiveCount( Qt::Horizontal ); + return m_data->elements.size(); } bool QskLinearLayoutEngine::setRetainSizeWhenHiddenAt( int index, bool on ) { - const bool isModified = - m_data->entryTable.setRetainSizeWhenHiddenAt( index, on ); + if ( auto element = m_data->elementAt( index ) ) + { + if ( on != element->retainSizeWhenHidden() ) + { + const bool isIgnored = element->isIgnored(); - if ( isModified ) - invalidate( CellCache | LayoutCache ); + element->setRetainSizeWhenHidden( on ); - return isModified; + if ( isIgnored != element->isIgnored() ) + { + if ( m_data->sumIgnored >= 0 ) + m_data->sumIgnored += on ? 1 : -1; + + invalidate( LayoutCache ); + return true; + } + } + } + + return false; } bool QskLinearLayoutEngine::retainSizeWhenHiddenAt( int index ) const { - return m_data->entryTable.retainSizeWhenHiddenAt( index ); + if ( const auto element = m_data->elementAt( index ) ) + return element->retainSizeWhenHidden(); + + return false; } bool QskLinearLayoutEngine::setStretchFactorAt( int index, int stretchFactor ) { - const bool isModified = - m_data->entryTable.setStretchFactorAt( index, stretchFactor ); + if ( auto element = m_data->elementAt( index ) ) + { + if ( stretchFactor < 0 ) + stretchFactor = -1; - if ( isModified ) - invalidate( CellCache | LayoutCache ); + if ( element->stretch() != stretchFactor ) + { + element->setStretch( stretchFactor ); + invalidate( LayoutCache ); - return isModified; + return true; + } + } + + return false; } int QskLinearLayoutEngine::stretchFactorAt( int index ) const { - return m_data->entryTable.stretchFactorAt( index ); + if ( const auto element = m_data->elementAt( index ) ) + return element->stretch(); + + return -1; } bool QskLinearLayoutEngine::setAlignmentAt( int index, Qt::Alignment alignment ) { - return m_data->entryTable.setAlignmentAt( index, alignment ); + if ( auto element = m_data->elementAt( index ) ) + { + if ( alignment != element->alignment() ) + element->setAlignment( alignment ); + + return true; + } + + return false; } Qt::Alignment QskLinearLayoutEngine::alignmentAt( int index ) const { - return m_data->entryTable.alignmentAt( index ); + if ( const auto element = m_data->elementAt( index ) ) + return element->alignment(); + + return Qt::Alignment(); } -bool QskLinearLayoutEngine::setSpacing( qreal spacing, Qt::Orientations orientations ) +int QskLinearLayoutEngine::insertItem( QQuickItem* item, int index ) { - if ( spacing < 0.0 ) - spacing = 0.0; + auto& elements = m_data->elements; - bool isModified = false; - - if ( orientations & Qt::Horizontal ) - isModified |= m_data->colChain.setSpacing( spacing ); - - if ( orientations & Qt::Vertical ) - isModified |= m_data->rowChain.setSpacing( spacing ); - - if ( isModified ) - invalidate( CellCache | LayoutCache ); - - return isModified; -} - -qreal QskLinearLayoutEngine::spacing( Qt::Orientation orientation ) const -{ - if ( orientation == Qt::Horizontal ) - return m_data->colChain.spacing(); + if ( index < 0 || index > count() ) + { + index = elements.size(); + elements.emplace_back( item ); + } else - return m_data->rowChain.spacing(); + { + elements.emplace( elements.begin() + index, item ); + } + + invalidate(); + return index; } -bool QskLinearLayoutEngine::setExtraSpacingAt( Qt::Edges edges ) +int QskLinearLayoutEngine::insertSpacerAt( int index, qreal spacing ) { - if ( edges == m_data->extraSpacingAt ) - return false; + spacing = qMax( spacing, static_cast< qreal >( 0.0 ) ); - m_data->extraSpacingAt = edges; + auto& elements = m_data->elements; - Qt::Edges colEdges = edges & ~( Qt::TopEdge | Qt::BottomEdge ); - m_data->colChain.setExtraSpacingAt( colEdges ); - - /* - FlowLayoutInfo does not have an orientation, so we always - set the position for potential fill spaces using Left/Right. - Maybe introducing another enum might be a good idea. TODO ... - */ - - Qt::Edges rowEdges; - if ( edges & Qt::TopEdge ) - rowEdges |= Qt::LeftEdge; - - if ( edges & Qt::BottomEdge ) - rowEdges |= Qt::RightEdge; - - m_data->rowChain.setExtraSpacingAt( rowEdges ); + if ( index < 0 || index > count() ) + { + index = elements.size(); + elements.emplace_back( spacing ); + } + else + { + elements.emplace( elements.begin() + index, spacing ); + } invalidate( LayoutCache ); + return index; +} + +bool QskLinearLayoutEngine::removeAt( int index ) +{ + auto element = m_data->elementAt( index ); + if ( element == nullptr ) + return false; + + if ( element->isIgnored() ) + m_data->sumIgnored--; + + const auto itemType = + QskLayoutConstraint::constraintType( element->item() ); + + int invalidationMode = LayoutCache; + + if ( itemType > QskLayoutConstraint::Unconstrained ) + invalidationMode |= ElementCache; + + m_data->elements.erase( m_data->elements.begin() + index ); + invalidate( invalidationMode ); + return true; } -Qt::Edges QskLinearLayoutEngine::extraSpacingAt() const +bool QskLinearLayoutEngine::clear() { - return m_data->extraSpacingAt; -} + if ( count() <= 0 ) + return false; -bool QskLinearLayoutEngine::setDefaultAlignment( Qt::Alignment alignment ) -{ - return m_data->entryTable.setDefaultAlignment( alignment ); -} + m_data->elements.clear(); + invalidate(); -Qt::Alignment QskLinearLayoutEngine::defaultAlignment() const -{ - return m_data->entryTable.defaultAlignment(); -} - -void QskLinearLayoutEngine::insertItem( QQuickItem* item, int index ) -{ - m_data->entryTable.insertItem( index, item ); - invalidate( CellCache | LayoutCache ); -} - -void QskLinearLayoutEngine::addItem( QQuickItem* item ) -{ - insertItem( item, -1 ); -} - -void QskLinearLayoutEngine::insertSpacerAt( int index, qreal spacing ) -{ - m_data->entryTable.insertSpacer( index, spacing ); - invalidate( CellCache | LayoutCache ); -} - -void QskLinearLayoutEngine::addSpacer( qreal spacing ) -{ - insertSpacerAt( -1, spacing ); -} - -void QskLinearLayoutEngine::removeAt( int index ) -{ - if ( m_data->entryTable.removeAt( index ) ) - invalidate( CellCache | LayoutCache ); -} - -void QskLinearLayoutEngine::clear() -{ - if ( m_data->entryTable.clear() ) - invalidate( CellCache | LayoutCache ); + return true; } QQuickItem* QskLinearLayoutEngine::itemAt( int index ) const { - return m_data->entryTable.itemAt( index ); + if ( const auto element = m_data->elementAt( index ) ) + return element->item(); + + return nullptr; } -int QskLinearLayoutEngine::spacerAt( int index ) const +qreal QskLinearLayoutEngine::spacerAt( int index ) const { - return m_data->entryTable.spacerAt( index ); + if ( const auto element = m_data->elementAt( index ) ) + return element->spacer(); + + return -1.0; } -void QskLinearLayoutEngine::invalidate( int what ) +void QskLinearLayoutEngine::layoutItems() { - if ( m_data->blockInvalidate ) - return; - - if ( what & EntryCache ) - m_data->entryTable.invalidate(); - - if ( what & CellCache ) - { - m_data->rowChain.invalidate(); - m_data->colChain.invalidate(); - } - - if ( what & LayoutCache ) - m_data->geometries.invalidate(); -} - -void QskLinearLayoutEngine::setGeometries( const QRectF& rect ) -{ - if ( m_data->entryTable.effectiveCount() == 0 ) - return; - - if ( m_data->geometries.boundingSize != rect.size() ) - { - m_data->blockInvalidate = true; - updateCellGeometries( rect.size() ); - m_data->blockInvalidate = false; - } - - /* - In case we have items that send LayoutRequest events on - geometry changes - what doesn't make much sense - we - better make a ( implicitely shared ) copy of the geometries. - */ - const auto geometries = m_data->geometries; - uint row = 0; uint col = 0; - const auto& entryTable = m_data->entryTable; - - for ( int i = 0; i < entryTable.count(); i++ ) + for ( const auto& element : m_data->elements ) { - if ( entryTable.isIgnoredAt( i ) ) + if ( element.isIgnored() ) continue; - if ( auto item = entryTable.itemAt( i ) ) + if ( auto item = element.item() ) { - QRectF r = geometries.geometryAt( row, col ); - r.translate( rect.x(), rect.y() ); - - const auto alignment = entryTable.effectiveAlignmentAt( i ); - - r = QskLayoutConstraint::itemRect( item, r, alignment ); - - if ( m_data->visualDirection == Qt::RightToLeft ) - r.moveRight( rect.right() - ( r.left() - rect.left() ) ); - - qskSetItemGeometry( item, r ); + const QRect grid( col, row, 1, 1 ); + layoutItem( item, grid, element.alignment() ); } - if ( entryTable.orientation() == Qt::Horizontal ) + if ( m_data->orientation == Qt::Horizontal ) { - if ( ++col == entryTable.dimension() ) + if ( ++col == m_data->dimension ) { col = 0; row++; @@ -916,7 +453,7 @@ void QskLinearLayoutEngine::setGeometries( const QRectF& rect ) } else { - if ( ++row == entryTable.dimension() ) + if ( ++row == m_data->dimension ) { row = 0; col++; @@ -925,123 +462,83 @@ void QskLinearLayoutEngine::setGeometries( const QRectF& rect ) } } -QSizeF QskLinearLayoutEngine::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const +int QskLinearLayoutEngine::effectiveCount( Qt::Orientation orientation ) const { - const auto& entryTable = m_data->entryTable; + const uint cellCount = effectiveCount(); - if ( entryTable.effectiveCount() == 0 ) - return QSizeF( 0.0, 0.0 ); - - const auto constraintType = m_data->entryTable.constraintType(); - - auto& colChain = m_data->colChain; - auto& rowChain = m_data->rowChain; - - m_data->blockInvalidate = true; - - if ( ( constraint.width() >= 0 ) && - ( constraintType == QskLayoutConstraint::HeightForWidth ) ) + if ( orientation == m_data->orientation ) { - qskUpdateLayoutChain( Qt::Horizontal, entryTable, colChain ); - - const auto cellConstraints = colChain.geometries( constraint.width() ); - qskUpdateLayoutChain( Qt::Vertical, cellConstraints, entryTable, rowChain ); - } - else if ( ( constraint.height() >= 0 ) && - ( constraintType == QskLayoutConstraint::WidthForHeight ) ) - { - qskUpdateLayoutChain( Qt::Vertical, entryTable, rowChain ); - - const auto cellConstraints = rowChain.geometries( constraint.height() ); - qskUpdateLayoutChain( Qt::Horizontal, cellConstraints, entryTable, colChain ); + return qMin( cellCount, m_data->dimension ); } else { - qskUpdateLayoutChain( Qt::Horizontal, entryTable, colChain ); - qskUpdateLayoutChain( Qt::Vertical, entryTable, rowChain ); + int count = cellCount / m_data->dimension; + if ( cellCount % m_data->dimension ) + count++; + + return count; } - - m_data->blockInvalidate = false; - - const qreal width = colChain.boundingHint().size( which ); - const qreal height = rowChain.boundingHint().size( which ); - - return QSizeF( width, height ); } -qreal QskLinearLayoutEngine::widthForHeight( qreal height ) const +int QskLinearLayoutEngine::effectiveCount() const { - const QSizeF constraint( -1, height ); - return sizeHint( Qt::PreferredSize, constraint ).width(); -} - -qreal QskLinearLayoutEngine::heightForWidth( qreal width ) const -{ - const QSizeF constraint( width, -1 ); - return sizeHint( Qt::PreferredSize, constraint ).height(); -} - -bool QskLinearLayoutEngine::setVisualDirection(Qt::LayoutDirection direction) -{ - if ( m_data->visualDirection != direction ) + if ( m_data->sumIgnored < 0 ) { - m_data->visualDirection = direction; - return true; + m_data->sumIgnored = 0; + + for ( const auto& element : m_data->elements ) + { + if ( element.isIgnored() ) + m_data->sumIgnored++; + } } - return false; + return m_data->elements.size() - m_data->sumIgnored; } -Qt::LayoutDirection QskLinearLayoutEngine::visualDirection() const +void QskLinearLayoutEngine::invalidateElementCache() { - return m_data->visualDirection; + m_data->sumIgnored = -1; } -void QskLinearLayoutEngine::updateCellGeometries( const QSizeF& size ) +void QskLinearLayoutEngine::setupChain( Qt::Orientation orientation, + const QskLayoutChain::Segments& constraints, QskLayoutChain& chain ) const { - auto& geometries = m_data->geometries; - geometries.boundingSize = size; + uint index1 = 0; + uint index2 = 0; - auto& colChain = m_data->colChain; - auto& rowChain = m_data->rowChain; - auto& entryTable = m_data->entryTable; + const bool isLayoutOrientation = ( orientation == m_data->orientation ); - const QVector< QskLayoutChain::Range > noConstraints; + qreal constraint = -1.0; - switch( entryTable.constraintType() ) + for ( const auto& element : m_data->elements ) { - case QskLayoutConstraint::WidthForHeight: + if ( element.isIgnored() ) + continue; + + if ( !constraints.isEmpty() ) + constraint = constraints[index1].length; + + const auto cell = element.cell( + orientation, isLayoutOrientation, constraint ); + + chain.expandCell( index2, cell ); + + if ( isLayoutOrientation ) { - qskUpdateLayoutChain( Qt::Vertical, entryTable, rowChain ); - geometries.rows = rowChain.geometries( size.height() ); - - qskUpdateLayoutChain( Qt::Horizontal, geometries.rows, entryTable, colChain ); - geometries.columns = colChain.geometries( size.width() ); - - break; + if ( ++index2 == m_data->dimension ) + { + index2 = 0; + index1++; + } } - case QskLayoutConstraint::HeightForWidth: + else { - qskUpdateLayoutChain( Qt::Horizontal, entryTable, colChain ); - geometries.columns = colChain.geometries( size.width() ); - - qskUpdateLayoutChain( Qt::Vertical, geometries.columns, entryTable, rowChain ); - geometries.rows = rowChain.geometries( size.height() ); - - break; - } - default: - { - qskUpdateLayoutChain( Qt::Horizontal, entryTable, colChain ); - geometries.columns = colChain.geometries( size.width() ); - - qskUpdateLayoutChain( Qt::Vertical, entryTable, rowChain ); - geometries.rows = rowChain.geometries( size.height() ); + if ( ++index1 == m_data->dimension ) + { + index1 = 0; + index2++; + } } } } - -qreal QskLinearLayoutEngine::defaultSpacing( Qt::Orientation ) const -{ - return qskDefaultSpacing(); -} diff --git a/src/layouts/QskLinearLayoutEngine.h b/src/layouts/QskLinearLayoutEngine.h index 5c864ae1..c81b48c6 100644 --- a/src/layouts/QskLinearLayoutEngine.h +++ b/src/layouts/QskLinearLayoutEngine.h @@ -7,6 +7,7 @@ #define QSK_LINEAR_LAYOUT_ENGINE_H #include "QskGlobal.h" +#include "QskLayoutEngine2D.h" #include #include @@ -15,11 +16,11 @@ class QQuickItem; class QSizeF; class QRectF; -class QskLinearLayoutEngine +class QskLinearLayoutEngine : public QskLayoutEngine2D { public: QskLinearLayoutEngine( Qt::Orientation, uint dimension ); - ~QskLinearLayoutEngine(); + ~QskLinearLayoutEngine() override; Qt::Orientation orientation() const; bool setOrientation( Qt::Orientation ); @@ -27,36 +28,19 @@ class QskLinearLayoutEngine bool setDimension( uint dimension ); uint dimension() const; - bool setDefaultAlignment( Qt::Alignment ); - Qt::Alignment defaultAlignment() const; + int count() const override final; - bool setExtraSpacingAt( Qt::Edges ); - Qt::Edges extraSpacingAt() const; + int insertItem( QQuickItem*, int index ); + int addItem( QQuickItem* ); - bool setVisualDirection( Qt::LayoutDirection ); - Qt::LayoutDirection visualDirection() const; + int insertSpacerAt( int index, qreal spacing ); + int addSpacer( qreal spacing ); - bool setSpacing( qreal spacing, Qt::Orientations ); - qreal spacing( Qt::Orientation ) const; + bool removeAt( int index ); + bool clear(); - qreal defaultSpacing( Qt::Orientation ) const; - - int count() const; - - int rowCount() const; - int columnCount() const; - - void insertItem( QQuickItem*, int index ); - void addItem( QQuickItem* ); - - void insertSpacerAt( int index, qreal spacing ); - void addSpacer( qreal spacing ); - - void removeAt( int index ); - void clear(); - - QQuickItem* itemAt( int index ) const; - int spacerAt( int index ) const; + QQuickItem* itemAt( int index ) const override final; + qreal spacerAt( int index ) const override final; bool setRetainSizeWhenHiddenAt( int index, bool on ); bool retainSizeWhenHiddenAt( int index ) const; @@ -67,30 +51,31 @@ class QskLinearLayoutEngine bool setStretchFactorAt( int index, int stretchFactor ); int stretchFactorAt( int index ) const; - void setGeometries( const QRectF& ); - - qreal widthForHeight( qreal height ) const; - qreal heightForWidth( qreal width ) const; - - QSizeF sizeHint( Qt::SizeHint, const QSizeF& contraint ) const; - - enum - { - EntryCache = 1 << 0, - CellCache = 1 << 1, - LayoutCache = 1 << 2 - }; - - void invalidate( int what = EntryCache | CellCache | LayoutCache ); - - private: - void updateCellGeometries( const QSizeF& ); - private: Q_DISABLE_COPY(QskLinearLayoutEngine) + void layoutItems() override; + + int effectiveCount() const; + int effectiveCount( Qt::Orientation ) const override; + + void invalidateElementCache() override; + + virtual void setupChain( Qt::Orientation, + const QskLayoutChain::Segments&, QskLayoutChain& ) const override; + class PrivateData; std::unique_ptr< PrivateData > m_data; }; +inline int QskLinearLayoutEngine::addItem( QQuickItem* item ) +{ + return insertItem( item, -1 ); +} + +inline int QskLinearLayoutEngine::addSpacer( qreal spacing ) +{ + return insertSpacerAt( -1, spacing ); +} + #endif diff --git a/src/src.pro b/src/src.pro index f2b7d76e..1f3d49b2 100644 --- a/src/src.pro +++ b/src/src.pro @@ -9,6 +9,8 @@ QSK_SUBDIRS = common graphic nodes controls layouts dialogs inputpanel INCLUDEPATH *= $${QSK_SUBDIRS} DEPENDPATH *= $${QSK_SUBDIRS} +# DEFINES += QSK_LAYOUT_COMPAT + HEADERS += \ common/QskAspect.h \ common/QskBoxBorderColors.h \ @@ -236,8 +238,9 @@ HEADERS += \ layouts/QskGridLayoutEngine.h \ layouts/QskIndexedLayoutBox.h \ layouts/QskLayoutConstraint.h \ - layouts/QskLayoutHint.h \ layouts/QskLayoutChain.h \ + layouts/QskLayoutEngine2D.cpp \ + layouts/QskLayoutHint.h \ layouts/QskLinearBox.h \ layouts/QskLinearLayoutEngine.h \ layouts/QskStackBoxAnimator.h \ @@ -247,9 +250,10 @@ SOURCES += \ layouts/QskGridBox.cpp \ layouts/QskGridLayoutEngine.cpp \ layouts/QskIndexedLayoutBox.cpp \ - layouts/QskLayoutConstraint.cpp \ - layouts/QskLayoutHint.cpp \ layouts/QskLayoutChain.cpp \ + layouts/QskLayoutConstraint.cpp \ + layouts/QskLayoutEngine2D.cpp \ + layouts/QskLayoutHint.cpp \ layouts/QskLinearBox.cpp \ layouts/QskLinearLayoutEngine.cpp \ layouts/QskStackBoxAnimator.cpp \