diff --git a/examples/automotive/MainWindow.cpp b/examples/automotive/MainWindow.cpp index 397c7710..963e1e15 100644 --- a/examples/automotive/MainWindow.cpp +++ b/examples/automotive/MainWindow.cpp @@ -33,7 +33,7 @@ MainWindow::MainWindow() layout->setStretchFactor( header, 1 ); layout->addItem( content ); - layout->setStretchFactor( content, 10 ); + layout->setStretchFactor( content, 8 ); layout->addItem( footer ); layout->setStretchFactor( footer, 1 ); diff --git a/examples/automotive/SoundControl.cpp b/examples/automotive/SoundControl.cpp index d6c832fa..5b63076a 100644 --- a/examples/automotive/SoundControl.cpp +++ b/examples/automotive/SoundControl.cpp @@ -389,8 +389,8 @@ SoundControl::SoundControl( QQuickItem* parent ) auto layout = new QskGridBox( this ); layout->setMargins( QMarginsF( 40, 20, 40, 20 ) ); - layout->setVerticalSpacing( 10 ); - layout->setHorizontalSpacing( 60 ); + layout->setSpacing( Qt::Vertical, 10 ); + layout->setSpacing( Qt::Horizontal, 60 ); layout->setColumnStretchFactor( 0, 1 ); layout->setColumnStretchFactor( 1, 2 ); diff --git a/examples/layouts/DynamicConstraintsPage.cpp b/examples/layouts/DynamicConstraintsPage.cpp index 7b2a7452..341c9d02 100644 --- a/examples/layouts/DynamicConstraintsPage.cpp +++ b/examples/layouts/DynamicConstraintsPage.cpp @@ -105,16 +105,13 @@ Box::Box( QQuickItem* parent ) void Box::flip() { - setActive( false ); - - for ( int i = 0; i < itemCount(); i++ ) + for ( int i = 0; i < entryCount(); i++ ) { - if ( Control* control = dynamic_cast< Control* >( itemAtIndex( i ) ) ) + if ( auto control = dynamic_cast< Control* >( itemAtIndex( i ) ) ) control->transpose(); } transpose(); - setActive( true ); } void Box::addControl( Control* control ) diff --git a/examples/layouts/FlowLayoutPage.cpp b/examples/layouts/FlowLayoutPage.cpp index 1648ab3d..2cbf81ed 100644 --- a/examples/layouts/FlowLayoutPage.cpp +++ b/examples/layouts/FlowLayoutPage.cpp @@ -76,7 +76,7 @@ namespace void addRectangle( const char* colorName ) { auto rect = new TestRectangle( colorName ); - rect->setText( QString::number( itemCount() + 1 ) ); + rect->setText( QString::number( entryCount() + 1 ) ); addItem( rect, Qt::AlignCenter ); } diff --git a/examples/layouts/LinearLayoutPage.cpp b/examples/layouts/LinearLayoutPage.cpp index 60ff1c5e..13bc9d09 100644 --- a/examples/layouts/LinearLayoutPage.cpp +++ b/examples/layouts/LinearLayoutPage.cpp @@ -73,7 +73,7 @@ namespace void addRectangle( const char* colorName ) { auto rect = new TestRectangle( colorName ); - rect->setText( QString::number( itemCount() + 1 ) ); + rect->setText( QString::number( entryCount() + 1 ) ); addItem( rect, Qt::AlignCenter ); } diff --git a/examples/sliders/main.cpp b/examples/sliders/main.cpp index 04e3d50e..b06f1492 100644 --- a/examples/sliders/main.cpp +++ b/examples/sliders/main.cpp @@ -131,7 +131,7 @@ class SliderBox : public QskLinearBox customSlider->setStepSize( 10 ); customSlider->setPageSize( 10 ); - for ( int i = 0; i < itemCount(); i++ ) + for ( int i = 0; i < entryCount(); i++ ) { if ( auto slider = qobject_cast< QskSlider* >( itemAtIndex( i ) ) ) { @@ -151,7 +151,7 @@ class SliderBox : public QskLinearBox { setOrientation( inverted( orientation() ) ); - for ( int i = 0; i < itemCount(); i++ ) + for ( int i = 0; i < entryCount(); i++ ) { if ( auto slider = qobject_cast< QskSlider* >( itemAtIndex( i ) ) ) { @@ -159,7 +159,7 @@ class SliderBox : public QskLinearBox slider->setOrientation( orientation ); - if ( i >= itemCount() - 1 ) + if ( i >= entryCount() - 1 ) { // we didn't implement the vertical mode of the heavily // customized slider yet. diff --git a/playground/dialogbuttons/Window.cpp b/playground/dialogbuttons/Window.cpp index 6edecb7d..f8ee6292 100644 --- a/playground/dialogbuttons/Window.cpp +++ b/playground/dialogbuttons/Window.cpp @@ -61,7 +61,6 @@ void Window::flipOrientation() newBox->setMargins( m_layoutBox->margins() ); m_orientation = invertedOrientation(); - m_layoutBox->setActive( false ); const QVector< QskDialogButtonBox* > boxes = dialogBoxes(); for ( QskDialogButtonBox* box : boxes ) @@ -88,7 +87,7 @@ void Window::centerButtons() QVector< QskDialogButtonBox* > Window::dialogBoxes() const { QVector< QskDialogButtonBox* > boxes; - for ( int i = 0; i < m_layoutBox->itemCount(); i++ ) + for ( int i = 0; i < m_layoutBox->entryCount(); i++ ) { if ( auto box = qobject_cast< QskDialogButtonBox* >( m_layoutBox->itemAtIndex( i ) ) ) { diff --git a/qmlexport/QskLayoutQml.cpp b/qmlexport/QskLayoutQml.cpp new file mode 100644 index 00000000..457e97b0 --- /dev/null +++ b/qmlexport/QskLayoutQml.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskLayoutQml.h" + +void QskGridBoxQml::setHorizontalSpacing( qreal spacing ) +{ + const auto oldSpacing = QskGridBox::spacing( Qt::Horizontal ); + QskGridBox::setSpacing( Qt::Horizontal, spacing ); + + if ( oldSpacing != QskGridBox::spacing( Qt::Horizontal ) ) + Q_EMIT verticalSpacingChanged(); +} + +qreal QskGridBoxQml::horizontalSpacing() const +{ + return QskGridBox::spacing( Qt::Horizontal ); +} + +void QskGridBoxQml::resetHorizontalSpacing() +{ + const auto orientation = Qt::Horizontal; + + const auto oldSpacing = QskGridBox::spacing( orientation ); + QskGridBox::resetSpacing( orientation ); + + if ( oldSpacing != QskGridBox::spacing( orientation ) ) + Q_EMIT horizontalSpacingChanged(); +} + +void QskGridBoxQml::setVerticalSpacing( qreal spacing ) +{ + const auto oldSpacing = QskGridBox::spacing( Qt::Vertical ); + QskGridBox::setSpacing( Qt::Vertical, spacing ); + + if ( oldSpacing != QskGridBox::spacing( Qt::Vertical ) ) + Q_EMIT verticalSpacingChanged(); +} + +qreal QskGridBoxQml::verticalSpacing() const +{ + return QskGridBox::spacing( Qt::Vertical ); +} + +void QskGridBoxQml::resetVerticalSpacing() +{ + const auto orientation = Qt::Vertical; + + const auto oldSpacing = QskGridBox::spacing( orientation ); + QskGridBox::resetSpacing( orientation ); + + if ( oldSpacing != QskGridBox::spacing( orientation ) ) + Q_EMIT verticalSpacingChanged(); +} + +#include "moc_QskLayoutQml.cpp" diff --git a/qmlexport/QskLayoutQml.h b/qmlexport/QskLayoutQml.h index 78de1bcd..1f43afd8 100644 --- a/qmlexport/QskLayoutQml.h +++ b/qmlexport/QskLayoutQml.h @@ -36,7 +36,7 @@ class QskLayoutBoxQml : public LayoutBox { return LayoutBox::indexOf( item ); } - + Q_INVOKABLE void removeAt( int index ) { return LayoutBox::removeAt( index ); @@ -46,7 +46,7 @@ class QskLayoutBoxQml : public LayoutBox { // QML does not like a const version LayoutBox::removeItem( item ); - } + } Q_INVOKABLE void setAlignment( QQuickItem* item, Qt::Alignment alignment ) { @@ -63,14 +63,14 @@ class QskStackBoxQml : public QskLayoutBoxQml< QskStackBox > { Q_OBJECT - Q_INVOKABLE void setAlignment( int index, Qt::Alignment alignment ) + Q_INVOKABLE void setAlignmentAt( int index, Qt::Alignment alignment ) { - QskStackBox::setAlignment( index, alignment ); + QskStackBox::setAlignmentAt( index, alignment ); } - Q_INVOKABLE Qt::Alignment alignment( int index ) const + Q_INVOKABLE Qt::Alignment alignmentAt( int index ) const { - return QskStackBox::alignment( index ); + return QskStackBox::alignmentAt( index ); } }; @@ -115,7 +115,22 @@ class QskGridBoxQml : public QskLayoutBoxQml< QskGridBox > { Q_OBJECT + Q_PROPERTY( qreal horizontalSpacing READ horizontalSpacing + WRITE setHorizontalSpacing RESET resetHorizontalSpacing + NOTIFY horizontalSpacingChanged ) + + Q_PROPERTY( qreal verticalSpacing READ verticalSpacing + WRITE setVerticalSpacing RESET resetVerticalSpacing + NOTIFY verticalSpacingChanged ) + public: + void setHorizontalSpacing( qreal ); + void resetHorizontalSpacing(); + qreal horizontalSpacing() const; + + void setVerticalSpacing( qreal ); + void resetVerticalSpacing(); + qreal verticalSpacing() const; Q_INVOKABLE bool retainSizeWhenHidden( QQuickItem* item ) const { @@ -126,6 +141,10 @@ class QskGridBoxQml : public QskLayoutBoxQml< QskGridBox > { QskGridBox::setRetainSizeWhenHidden( item, on ); } + + Q_SIGNALS: + void verticalSpacingChanged(); + void horizontalSpacingChanged(); }; #endif diff --git a/qmlexport/qmlexport.pro b/qmlexport/qmlexport.pro index 16140deb..8058d840 100644 --- a/qmlexport/qmlexport.pro +++ b/qmlexport/qmlexport.pro @@ -14,6 +14,7 @@ HEADERS += \ SOURCES += \ QskShortcutQml.cpp \ + QskLayoutQml.cpp \ QskQml.cpp target.path = $${QSK_INSTALL_LIBS} diff --git a/src/controls/QskTabBar.cpp b/src/controls/QskTabBar.cpp index ce10ec21..0119d87d 100644 --- a/src/controls/QskTabBar.cpp +++ b/src/controls/QskTabBar.cpp @@ -41,7 +41,7 @@ namespace QskControl::keyPressEvent. */ - for ( int i = 0; i < itemCount(); i++ ) + for ( int i = 0; i < entryCount(); i++ ) { if ( auto button = itemAtIndex( i ) ) button->setZ( i == currentIndex ? 0.001 : 0.0 ); @@ -179,8 +179,8 @@ int QskTabBar::insertTab( int index, QskTabButton* button ) { auto buttonBox = m_data->buttonBox; - if ( index < 0 || index >= buttonBox->itemCount() ) - index = buttonBox->itemCount(); + if ( index < 0 || index >= buttonBox->entryCount() ) + index = buttonBox->entryCount(); if ( isComponentComplete() ) { @@ -323,7 +323,7 @@ int QskTabBar::currentIndex() const int QskTabBar::count() const { - return m_data->buttonBox->itemCount(); + return m_data->buttonBox->entryCount(); } QskTabButton* QskTabBar::buttonAt( int position ) diff --git a/src/dialogs/QskDialogButtonBox.cpp b/src/dialogs/QskDialogButtonBox.cpp index d1bb0f0e..28397b22 100644 --- a/src/dialogs/QskDialogButtonBox.cpp +++ b/src/dialogs/QskDialogButtonBox.cpp @@ -160,9 +160,6 @@ void QskDialogButtonBox::rearrangeButtons() auto layoutBox = m_data->layoutBox; - const bool isActive = layoutBox->isActive(); - layoutBox->setActive( false ); - layoutBox->clear(); const int* currentLayout = effectiveSkin()->dialogButtonLayout( orientation() ); @@ -226,8 +223,6 @@ void QskDialogButtonBox::rearrangeButtons() if ( m_data->centeredButtons ) layoutBox->addStretch( 1 ); - layoutBox->setActive( isActive ); - // reorganizing the tab chain ??? } @@ -472,7 +467,10 @@ QskDialog::Action QskDialogButtonBox::clickedAction() const bool QskDialogButtonBox::event( QEvent* event ) { if ( event->type() == QEvent::LayoutRequest ) - resetImplicitSize(); + { + if ( !m_data->dirtyLayout ) + resetImplicitSize(); + } return Inherited::event( event ); } diff --git a/src/layouts/QskGridBox.cpp b/src/layouts/QskGridBox.cpp index 0cffcef0..7ca49191 100644 --- a/src/layouts/QskGridBox.cpp +++ b/src/layouts/QskGridBox.cpp @@ -4,24 +4,54 @@ *****************************************************************************/ #include "QskGridBox.h" -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" +#include "QskGridLayoutEngine.h" +#include "QskLayoutConstraint.h" +#include "QskEvent.h" + +static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on ) +{ + if ( ( item == nullptr ) || ( qskControlCast( item ) != nullptr ) ) + return; + + /* + For QQuickItems not being derived from QskControl we manually + send QEvent::LayoutRequest events. + */ + + if ( on ) + { + auto sendLayoutRequest = + [receiver]() + { + QEvent event( QEvent::LayoutRequest ); + QCoreApplication::sendEvent( receiver, &event ); + }; + + QObject::connect( item, &QQuickItem::implicitWidthChanged, + receiver, sendLayoutRequest ); + + QObject::connect( item, &QQuickItem::implicitHeightChanged, + receiver, sendLayoutRequest ); + + QObject::connect( item, &QQuickItem::visibleChanged, + receiver, sendLayoutRequest ); + } + else + { + QObject::disconnect( item, &QQuickItem::implicitWidthChanged, receiver, nullptr ); + QObject::disconnect( item, &QQuickItem::implicitHeightChanged, receiver, nullptr ); + QObject::disconnect( item, &QQuickItem::visibleChanged, receiver, nullptr ); + } +} class QskGridBox::PrivateData { public: - PrivateData() - : isExpanding( false ) - , unlimitedSpanned( 0 ) - { - } - - bool isExpanding; - unsigned int unlimitedSpanned; + QskGridLayoutEngine engine; }; QskGridBox::QskGridBox( QQuickItem* parent ) - : QskLayoutBox( parent ) + : QskBox( false, parent ) , m_data( new PrivateData() ) { } @@ -34,400 +64,444 @@ void QskGridBox::addItem( QQuickItem* item, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment ) { - auto layoutItem = new QskLayoutItem( item, row, column, rowSpan, columnSpan ); - layoutItem->setAlignment( alignment ); + if ( item == nullptr ) + return; - const int index = itemCount(); // position and index doesn't match + if ( item->parent() == nullptr ) + item->setParent( this ); - setupLayoutItem( layoutItem, index ); - insertItemInternal( layoutItem, -1 ); - layoutItemInserted( layoutItem, index ); + if ( item->parentItem() != this ) + item->setParentItem( this ); + + qskSetItemActive( this, item, true ); + + // What about the focus tab chain - TODO ... ???? + // check if item is already inserted ??? + + m_data->engine.insertItem( + item, row, column, rowSpan, columnSpan, alignment ); + + resetImplicitSize(); + polish(); + +} + +void QskGridBox::removeAt( int index ) +{ + auto& engine = m_data->engine; + + if ( auto item = engine.itemAt( index ) ) + qskSetItemActive( this, item, false ); + + engine.removeAt( index ); + + resetImplicitSize(); + polish(); +} + +void QskGridBox::removeItem( const QQuickItem* item ) +{ + removeAt( indexOf( item ) ); +} + +void QskGridBox::clear( bool autoDelete ) +{ + for ( int i = itemCount() - 1; i >= 0; i-- ) + { + auto item = itemAtIndex( i ); + + removeAt( i ); + + if( item ) + { + if( autoDelete && ( item->parent() == this ) ) + delete item; + else + item->setParentItem( nullptr ); + } + } +} + +int QskGridBox::itemCount() const +{ + return m_data->engine.itemCount(); } int QskGridBox::rowCount() const { - return engine().rowCount(); + return m_data->engine.rowCount(); } int QskGridBox::columnCount() const { - return engine().columnCount(); + return m_data->engine.columnCount(); +} + +QQuickItem* QskGridBox::itemAtIndex( int index ) const +{ + return m_data->engine.itemAt( index ); +} + +int QskGridBox::indexOf( const QQuickItem* item ) const +{ + return m_data->engine.indexOf( item ); } QQuickItem* QskGridBox::itemAt( int row, int column ) const { - if ( const auto layoutItem = engine().layoutItemAt( row, column ) ) - return layoutItem->item(); - - return nullptr; + return m_data->engine.itemAt( row, column ); } int QskGridBox::indexAt( int row, int column ) const { - return engine().indexAt( row, column ); + return m_data->engine.indexAt( row, column ); } int QskGridBox::rowOfIndex( int index ) const { - if ( auto layoutItem = engine().layoutItemAt( index ) ) - return layoutItem->firstRow(); - - return -1; + return m_data->engine.rowOfIndex( index ); } int QskGridBox::rowSpanOfIndex( int index ) const { - if ( auto layoutItem = engine().layoutItemAt( index ) ) - return layoutItem->rowSpan(); - - return 0; + return m_data->engine.rowSpanOfIndex( index ); } int QskGridBox::columnOfIndex( int index ) const { - if ( auto layoutItem = engine().layoutItemAt( index ) ) - return layoutItem->firstColumn(); - - return -1; + return m_data->engine.columnOfIndex( index ); } int QskGridBox::columnSpanOfIndex( int index ) const { - if ( auto layoutItem = engine().layoutItemAt( index ) ) - return layoutItem->columnSpan(); - - return 0; + return m_data->engine.columnSpanOfIndex( index ); } -void QskGridBox::setupLayoutItem( QskLayoutItem* layoutItem, int index ) -{ - Q_UNUSED( index ) - - auto& engine = this->engine(); - - m_data->isExpanding = ( layoutItem->lastColumn() >= engine.columnCount() ) || - ( layoutItem->lastRow() >= engine.rowCount() ); -} - -void QskGridBox::layoutItemInserted( QskLayoutItem* layoutItem, int index ) -{ - Q_UNUSED( index ) - - if ( m_data->isExpanding ) - { - // 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 ) - engine().adjustSpans( columnCount(), rowCount() ); - } - - 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 QskGridBox::layoutItemRemoved( QskLayoutItem* layoutItem, int index ) -{ - Q_UNUSED( index ) - - if ( layoutItem->hasUnlimitedSpan() ) - m_data->unlimitedSpanned--; - - QskLayoutEngine& engine = this->engine(); - - // cleanup rows/columns - - const QSize cells = engine.requiredCells(); - - const int numPendingColumns = engine.columnCount() - cells.width(); - const int numPendingRows = engine.rowCount() - cells.height(); - - if ( numPendingColumns > 0 || numPendingRows > 0 ) - { - if ( m_data->unlimitedSpanned > 0 ) - engine.adjustSpans( cells.height(), cells.width() ); - - engine.removeRows( cells.width(), numPendingColumns, Qt::Horizontal ); - engine.removeRows( cells.height(), numPendingRows, Qt::Vertical ); - } -} - -void QskGridBox::setSpacing( qreal spacing ) -{ - setHorizontalSpacing( spacing ); - setVerticalSpacing( spacing ); -} - -void QskGridBox::setHorizontalSpacing( qreal spacing ) +void QskGridBox::setSpacing( Qt::Orientations orientations, qreal spacing ) { spacing = qMax( spacing, 0.0 ); - if ( spacing != engine().spacing( Qt::Horizontal ) ) - { - engine().setSpacing( spacing, Qt::Horizontal ); - activate(); + bool doUpdate = false; - Q_EMIT horizontalSpacingChanged(); + 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 ) + { + resetImplicitSize(); + polish(); } } -qreal QskGridBox::horizontalSpacing() const +void QskGridBox::resetSpacing( Qt::Orientations orientations ) { - return engine().spacing( Qt::Horizontal ); -} - -void QskGridBox::resetHorizontalSpacing() -{ - const qreal spacing = QskLayoutEngine::defaultSpacing( Qt::Horizontal ); - setHorizontalSpacing( spacing ); -} - -void QskGridBox::setVerticalSpacing( qreal spacing ) -{ - spacing = qMax( spacing, 0.0 ); - - if ( spacing != engine().spacing( Qt::Vertical ) ) + for ( const auto o : { Qt::Horizontal, Qt::Vertical } ) { - engine().setSpacing( spacing, Qt::Vertical ); - activate(); - - Q_EMIT verticalSpacingChanged(); + if ( orientations & o ) + setSpacing( o, QskGridLayoutEngine::defaultSpacing( o ) ); } } -qreal QskGridBox::verticalSpacing() const +qreal QskGridBox::spacing( Qt::Orientation orientation ) const { - return engine().spacing( Qt::Vertical ); -} - -void QskGridBox::resetVerticalSpacing() -{ - const qreal spacing = QskLayoutEngine::defaultSpacing( Qt::Vertical ); - setVerticalSpacing( spacing ); + return m_data->engine.spacing( orientation ); } void QskGridBox::setRowSpacing( int row, qreal spacing ) { spacing = qMax( spacing, 0.0 ); - if ( spacing != engine().rowSpacing( row, Qt::Vertical ) ) + auto& engine = m_data->engine; + + if ( spacing != engine.spacingAt( Qt::Vertical, row ) ) { - engine().setRowSpacing( row, spacing, Qt::Vertical ); - activate(); + engine.setSpacingAt( Qt::Vertical, row, spacing ); + polish(); } } qreal QskGridBox::rowSpacing( int row ) const { - return engine().rowSpacing( row, Qt::Vertical ); + return m_data->engine.spacingAt( Qt::Vertical, row ); } void QskGridBox::setColumnSpacing( int column, qreal spacing ) { spacing = qMax( spacing, 0.0 ); - if ( spacing != engine().rowSpacing( column, Qt::Horizontal ) ) + auto& engine = m_data->engine; + + if ( spacing != engine.spacingAt( Qt::Horizontal, column ) ) { - engine().setRowSpacing( column, spacing, Qt::Horizontal ); - activate(); + engine.setSpacingAt( Qt::Horizontal, column, spacing ); + polish(); } } qreal QskGridBox::columnSpacing( int column ) const { - return engine().rowSpacing( column, Qt::Horizontal ); + return m_data->engine.spacingAt( Qt::Horizontal, column ); } void QskGridBox::setRowStretchFactor( int row, int stretch ) { - if ( stretch != engine().rowStretchFactor( row, Qt::Vertical ) ) + auto& engine = m_data->engine; + + if ( stretch != engine.stretchFactorAt( Qt::Vertical, row ) ) { - engine().setRowStretchFactor( row, stretch, Qt::Vertical ); - activate(); + engine.setStretchFactorAt( Qt::Vertical, row, stretch ); + polish(); } } int QskGridBox::rowStretchFactor( int row ) const { - return engine().rowStretchFactor( row, Qt::Vertical ); + return m_data->engine.stretchFactorAt( Qt::Vertical, row ); } void QskGridBox::setColumnStretchFactor( int column, int stretch ) { - if ( stretch != engine().rowStretchFactor( column, Qt::Horizontal ) ) + auto& engine = m_data->engine; + + if ( stretch != engine.stretchFactorAt( Qt::Horizontal, column ) ) { - engine().setRowStretchFactor( column, stretch, Qt::Horizontal ); - activate(); + engine.setStretchFactorAt( Qt::Horizontal, column, stretch ); + polish(); } } int QskGridBox::columnStretchFactor( int column ) const { - return engine().rowStretchFactor( column, Qt::Horizontal ); -} - -void QskGridBox::setRowMinimumHeight( int row, qreal height ) -{ - setRowSizeHint( Qt::MinimumSize, row, height, Qt::Vertical ); -} - -qreal QskGridBox::rowMinimumHeight( int row ) const -{ - return engine().rowSizeHint( Qt::MinimumSize, row, Qt::Vertical ); -} - -void QskGridBox::setRowPreferredHeight( int row, qreal height ) -{ - setRowSizeHint( Qt::PreferredSize, row, height, Qt::Vertical ); -} - -qreal QskGridBox::rowPreferredHeight( int row ) const -{ - return engine().rowSizeHint( Qt::PreferredSize, row, Qt::Vertical ); -} - -void QskGridBox::setRowMaximumHeight( int row, qreal height ) -{ - setRowSizeHint( Qt::MaximumSize, row, height, Qt::Vertical ); -} - -qreal QskGridBox::rowMaximumHeight( int row ) const -{ - return engine().rowSizeHint( Qt::MaximumSize, row, Qt::Vertical ); + return m_data->engine.stretchFactorAt( Qt::Horizontal, column ); } void QskGridBox::setRowFixedHeight( int row, qreal height ) { - setRowMinimumHeight( row, height ); - setRowMaximumHeight( row, height ); -} - -void QskGridBox::setColumnMinimumWidth( int column, qreal width ) -{ - setRowSizeHint( Qt::MinimumSize, column, width, Qt::Horizontal ); -} - -qreal QskGridBox::columnMinimumWidth( int column ) const -{ - return engine().rowSizeHint( Qt::MinimumSize, column, Qt::Horizontal ); -} - -void QskGridBox::setColumnPreferredWidth( int column, qreal width ) -{ - setRowSizeHint( Qt::PreferredSize, column, width, Qt::Horizontal ); -} - -qreal QskGridBox::columnPreferredWidth( int column ) const -{ - return engine().rowSizeHint( Qt::PreferredSize, column, Qt::Horizontal ); -} - -void QskGridBox::setColumnMaximumWidth( int column, qreal width ) -{ - setRowSizeHint( Qt::MaximumSize, column, width, Qt::Horizontal ); -} - -qreal QskGridBox::columnMaximumWidth( int column ) const -{ - return engine().rowSizeHint( Qt::MaximumSize, column, Qt::Horizontal ); + setRowSizeHint( row, Qt::MinimumSize, height ); + setRowSizeHint( row, Qt::MaximumSize, height ); } void QskGridBox::setColumnFixedWidth( int column, qreal width ) { - setColumnMinimumWidth( column, width ); - setColumnMaximumWidth( column, width ); + setColumnSizeHint( column, Qt::MinimumSize, width ); + setColumnSizeHint( column, Qt::MaximumSize, width ); } void QskGridBox::setRowAlignment( int row, Qt::Alignment alignment ) { - if ( engine().rowAlignment( row, Qt::Vertical ) != alignment ) + auto& engine = m_data->engine; + + if ( engine.alignmentAt( Qt::Vertical, row ) != alignment ) { - engine().setRowAlignment( row, alignment, Qt::Vertical ); - activate(); + engine.setAlignmentAt( Qt::Vertical, row, alignment ); + polish(); } } Qt::Alignment QskGridBox::rowAlignment( int row ) const { - return engine().rowAlignment( row, Qt::Vertical ); + return m_data->engine.alignmentAt( Qt::Vertical, row ); } void QskGridBox::setColumnAlignment( int column, Qt::Alignment alignment ) { - if ( alignment != engine().rowAlignment( column, Qt::Horizontal ) ) + auto& engine = m_data->engine; + + if ( engine.alignmentAt( Qt::Horizontal, column ) != alignment ) { - engine().setRowAlignment( column, alignment, Qt::Horizontal ); - activate(); + engine.setAlignmentAt( Qt::Horizontal, column, alignment ); + polish(); } } Qt::Alignment QskGridBox::columnAlignment( int column ) const { - return engine().rowAlignment( column, Qt::Horizontal ); + return m_data->engine.alignmentAt( Qt::Horizontal, column ); } void QskGridBox::setAlignment( const QQuickItem* item, Qt::Alignment alignment ) { - QskLayoutItem* layoutItem = engine().layoutItemOf( item ); - if ( layoutItem && layoutItem->alignment() != alignment ) + auto& engine = m_data->engine; + + if ( engine.alignmentOf( item ) != alignment ) { - layoutItem->setAlignment( alignment ); - activate(); + engine.setAlignmentOf( item, alignment ); + polish(); } } Qt::Alignment QskGridBox::alignment( const QQuickItem* item ) const { - QskLayoutItem* layoutItem = engine().layoutItemOf( item ); - if ( layoutItem ) - return layoutItem->alignment(); - - return Qt::Alignment(); + return m_data->engine.alignmentOf( item ); } void QskGridBox::setRetainSizeWhenHidden( const QQuickItem* item, bool on ) { - QskLayoutItem* layoutItem = engine().layoutItemOf( item ); - if ( layoutItem && on != layoutItem->retainSizeWhenHidden() ) + auto& engine = m_data->engine; + + if ( engine.retainSizeWhenHiddenOf( item ) != on ) { - layoutItem->setRetainSizeWhenHidden( on ); + engine.setRetainSizeWhenHiddenOf( item, on ); invalidate(); } } bool QskGridBox::retainSizeWhenHidden( const QQuickItem* item ) const { - QskLayoutItem* layoutItem = engine().layoutItemOf( item ); - if ( layoutItem ) - return layoutItem->retainSizeWhenHidden(); - - return false; + return m_data->engine.retainSizeWhenHiddenOf( item ); } -void QskGridBox::setRowSizeHint( - Qt::SizeHint which, int row, qreal size, Qt::Orientation orientation ) +void QskGridBox::setRowSizeHint( int row, Qt::SizeHint which, qreal height ) { - if ( size != engine().rowSizeHint( which, row, orientation ) ) + auto& engine = m_data->engine; + + if ( height != engine.rowSizeHint( row, which ) ) { - engine().setRowSizeHint( which, row, size, orientation ); - activate(); + engine.setRowSizeHint( row, which, height ); + polish(); } } +qreal QskGridBox::rowSizeHint( int row, Qt::SizeHint which ) const +{ + return m_data->engine.rowSizeHint( row, which ); +} + +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 ); + polish(); + } +} + +qreal QskGridBox::columnSizeHint( int column, Qt::SizeHint which ) const +{ + return m_data->engine.columnSizeHint( column, which ); +} + +void QskGridBox::invalidate() +{ + m_data->engine.invalidate(); + + resetImplicitSize(); + polish(); +} + +void QskGridBox::updateLayout() +{ + m_data->engine.setGeometries( layoutRect() ); +} + +QSizeF QskGridBox::contentsSizeHint() const +{ + if ( itemCount() == 0 ) + return QSizeF( 0, 0 ); + + return m_data->engine.sizeHint( Qt::PreferredSize, QSizeF() ); +} + +qreal QskGridBox::heightForWidth( qreal width ) const +{ + auto constrainedHeight = + [this]( QskLayoutConstraint::Type, const QskControl*, qreal width ) + { + return m_data->engine.heightForWidth( width ); + }; + + return QskLayoutConstraint::constrainedMetric( + QskLayoutConstraint::HeightForWidth, this, width, constrainedHeight ); +} + +qreal QskGridBox::widthForHeight( qreal height ) const +{ + auto constrainedWidth = + [this]( QskLayoutConstraint::Type, const QskControl*, qreal height ) + { + return m_data->engine.widthForHeight( height ); + }; + + return QskLayoutConstraint::constrainedMetric( + QskLayoutConstraint::WidthForHeight, this, height, constrainedWidth ); +} + +void QskGridBox::geometryChangeEvent( QskGeometryChangeEvent* event ) +{ + Inherited::geometryChangeEvent( event ); + + if ( event->isResized() ) + polish(); +} + +void QskGridBox::itemChange( ItemChange change, const ItemChangeData& value ) +{ + Inherited::itemChange( change, value ); + + switch ( change ) + { + case ItemChildRemovedChange: + { + removeItem( value.item ); + break; + } + case QQuickItem::ItemVisibleHasChanged: + { + if ( value.boolValue ) + polish(); + break; + } + case QQuickItem::ItemSceneChange: + { + if ( value.window ) + polish(); + break; + } + default: + break; + } +} + +bool QskGridBox::event( QEvent* event ) +{ + switch ( event->type() ) + { + case QEvent::LayoutRequest: + { + invalidate(); + break; + } + case QEvent::LayoutDirectionChange: + { + m_data->engine.setVisualDirection( + layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight ); + + polish(); + break; + } + case QEvent::ContentsRectChange: + { + polish(); + break; + } + default: + break; + } + + return Inherited::event( event ); +} + #include "moc_QskGridBox.cpp" diff --git a/src/layouts/QskGridBox.h b/src/layouts/QskGridBox.h index 0341819c..aa8fcfbb 100644 --- a/src/layouts/QskGridBox.h +++ b/src/layouts/QskGridBox.h @@ -6,21 +6,16 @@ #ifndef QSK_GRID_BOX_H #define QSK_GRID_BOX_H -#include "QskLayoutBox.h" +#include "QskBox.h" -class QSK_EXPORT QskGridBox : public QskLayoutBox +class QSK_EXPORT QskGridBox : public QskBox { Q_OBJECT - Q_PROPERTY( qreal horizontalSpacing READ horizontalSpacing - WRITE setHorizontalSpacing RESET resetHorizontalSpacing - NOTIFY horizontalSpacingChanged ) + Q_PROPERTY( bool empty READ isEmpty() ) + Q_PROPERTY( int itemCount READ itemCount() ) - Q_PROPERTY( qreal verticalSpacing READ verticalSpacing - WRITE setVerticalSpacing RESET resetVerticalSpacing - NOTIFY verticalSpacingChanged ) - - using Inherited = QskLayoutBox; + using Inherited = QskBox; public: explicit QskGridBox( QQuickItem* parent = nullptr ); @@ -34,9 +29,18 @@ class QSK_EXPORT QskGridBox : public QskLayoutBox QQuickItem*, int row, int column, Qt::Alignment alignment = Qt::Alignment() ); + void removeItem( const QQuickItem* ); + void removeAt( int index ); + Q_INVOKABLE int rowCount() const; Q_INVOKABLE int columnCount() const; + int itemCount() const; + QQuickItem* itemAtIndex( int index ) const; + int indexOf( const QQuickItem* ) const; + + bool isEmpty() const; + Q_INVOKABLE QQuickItem* itemAt( int row, int column ) const; Q_INVOKABLE int indexAt( int row, int column ) const; @@ -47,15 +51,29 @@ class QSK_EXPORT QskGridBox : public QskLayoutBox Q_INVOKABLE int columnSpanOfIndex( int index ) const; // spacings - void setSpacing( qreal spacing ); - void setHorizontalSpacing( qreal spacing ); - void resetHorizontalSpacing(); - qreal horizontalSpacing() const; + void setSpacing( Qt::Orientations, qreal spacing ); + void resetSpacing( Qt::Orientations ); + qreal spacing( Qt::Orientation ) const; - void setVerticalSpacing( qreal spacing ); - void resetVerticalSpacing(); - qreal verticalSpacing() const; +#ifdef QSK_LAYOUT_COMPAT + void setVerticalSpacing( qreal spacing ) { setSpacing( Qt::Vertical, spacing ); } + qreal verticalSpacing() const { return spacing( Qt::Vertical ); } + + 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 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; @@ -71,26 +89,14 @@ class QSK_EXPORT QskGridBox : public QskLayoutBox Q_INVOKABLE int columnStretchFactor( int column ) const; // row/column size hints - Q_INVOKABLE void setRowMinimumHeight( int row, qreal height ); - Q_INVOKABLE qreal rowMinimumHeight( int row ) const; - Q_INVOKABLE void setRowPreferredHeight( int row, qreal height ); - Q_INVOKABLE qreal rowPreferredHeight( int row ) const; + Q_INVOKABLE void setColumnSizeHint( int column, Qt::SizeHint, qreal width ); + Q_INVOKABLE qreal columnSizeHint( int column, Qt::SizeHint ) const; - Q_INVOKABLE void setRowMaximumHeight( int row, qreal height ); - Q_INVOKABLE qreal rowMaximumHeight( int row ) const; + Q_INVOKABLE void setRowSizeHint( int row, Qt::SizeHint, qreal height ); + Q_INVOKABLE qreal rowSizeHint( int row, Qt::SizeHint ) const; Q_INVOKABLE void setRowFixedHeight( int row, qreal height ); - - Q_INVOKABLE void setColumnMinimumWidth( int column, qreal width ); - Q_INVOKABLE qreal columnMinimumWidth( int column ) const; - - Q_INVOKABLE void setColumnPreferredWidth( int column, qreal width ); - Q_INVOKABLE qreal columnPreferredWidth( int column ) const; - - Q_INVOKABLE void setColumnMaximumWidth( int column, qreal width ); - Q_INVOKABLE qreal columnMaximumWidth( int column ) const; - Q_INVOKABLE void setColumnFixedWidth( int column, qreal width ); // alignments @@ -107,20 +113,23 @@ class QSK_EXPORT QskGridBox : public QskLayoutBox bool retainSizeWhenHidden( const QQuickItem* ) const; void setRetainSizeWhenHidden( const QQuickItem*, bool on ); - Q_SIGNALS: - void verticalSpacingChanged(); - void horizontalSpacingChanged(); + QSizeF contentsSizeHint() const override; + + qreal heightForWidth( qreal width ) const override; + qreal widthForHeight( qreal height ) const override; + + public Q_SLOTS: + void invalidate(); + void clear( bool autoDelete = false ); protected: - void setupLayoutItem( QskLayoutItem*, int index ) override; - void layoutItemInserted( QskLayoutItem*, int index ) override; - void layoutItemRemoved( QskLayoutItem*, int index ) override; + bool event( QEvent* ) override; + void geometryChangeEvent( QskGeometryChangeEvent* ) override; + + void itemChange( ItemChange, const ItemChangeData& ) override; + void updateLayout() override; private: - void setRowSizeHint( - Qt::SizeHint which, int row, qreal size, - Qt::Orientation orientation ); - class PrivateData; std::unique_ptr< PrivateData > m_data; }; @@ -131,4 +140,9 @@ inline void QskGridBox::addItem( addItem( item, row, column, 1, 1, alignment ); } +inline bool QskGridBox::isEmpty() const +{ + return itemCount() <= 0; +} + #endif diff --git a/src/layouts/QskGridLayoutEngine.cpp b/src/layouts/QskGridLayoutEngine.cpp new file mode 100644 index 00000000..c5303812 --- /dev/null +++ b/src/layouts/QskGridLayoutEngine.cpp @@ -0,0 +1,554 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskGridLayoutEngine.h" +#include "QskLayoutConstraint.h" +#include "QskSizePolicy.h" +#include "QskControl.h" +#include "QskQuick.h" + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +namespace +{ + class LayoutStyleInfo final : public QAbstractLayoutStyleInfo + { + public: + qreal spacing( Qt::Orientation ) const override + { + // 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 ) ) + { + const auto policy = control->sizePolicy().horizontalPolicy(); + + return ( policy == QskSizePolicy::Constrained ) + ? Qt::Horizontal : Qt::Vertical; + } + + return orientation; + } + + bool isIgnored() const override final + { + if ( m_item && !qskIsVisibleToParent( m_item ) ) + return !m_retainSizeWhenHidden; + + return false; + } + + QLayoutPolicy::ControlTypes controlTypes( LayoutSide ) const override + { + return QLayoutPolicy::DefaultType; + } + + bool retainSizeWhenHidden() const + { + return m_retainSizeWhenHidden; + } + + void setRetainSizeWhenHidden( bool on ) + { + m_retainSizeWhenHidden = on; + } + + bool hasUnlimitedSpan() const + { + return m_unlimitedColumnSpan || m_unlimitedRowSpan; + } + + bool hasUnlimitedSpan( Qt::Orientation orientation ) const + { + return ( orientation == Qt::Horizontal ) + ? m_unlimitedColumnSpan : m_unlimitedRowSpan; + } + + 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*/ ) + { + /* + 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. + */ + } + + LayoutItem* layoutItemAt( int index ) const + { + if ( index < 0 || index >= q_items.count() ) + return nullptr; + + return static_cast< LayoutItem* >( q_items[ index ] ); + } + + 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 ) + { + const auto layoutItem = static_cast< const LayoutItem* >( q_items[ i ] ); + if ( layoutItem->item() == item ) + return i; + } + + return -1; + } + + qreal spacing( Qt::Orientation orientation ) const + { + const LayoutStyleInfo styleInfo; + return QGridLayoutEngine::spacing( orientation, &styleInfo ); + } + }; +} + +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; +}; + + +QskGridLayoutEngine::QskGridLayoutEngine() + : m_data( new PrivateData() ) +{ +} + +QskGridLayoutEngine::~QskGridLayoutEngine() +{ +} + +void QskGridLayoutEngine::setGeometries( const QRectF rect ) +{ + const LayoutStyleInfo styleInfo; + m_data->qengine.setGeometries( rect, &styleInfo ); +} + +void QskGridLayoutEngine::invalidate() +{ + m_data->qengine.invalidate(); +} + +void QskGridLayoutEngine::setVisualDirection( Qt::LayoutDirection direction ) +{ + m_data->qengine.setVisualDirection( direction ); +} + +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 ) + { + // 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() ); + } + + 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 ) +{ + m_data->qengine.setRowSizeHint( which, row, height, Qt::Vertical ); +} + +qreal QskGridLayoutEngine::rowSizeHint( int row, Qt::SizeHint which ) const +{ + return m_data->qengine.rowSizeHint( which, row, Qt::Vertical ); +} + +void QskGridLayoutEngine::setColumnSizeHint( int column, Qt::SizeHint which, qreal width ) +{ + m_data->qengine.setRowSizeHint( which, column, width, Qt::Horizontal ); +} + +qreal QskGridLayoutEngine::columnSizeHint( int column, Qt::SizeHint which ) const +{ + return m_data->qengine.rowSizeHint( which, column, Qt::Horizontal ); +} + +QSizeF QskGridLayoutEngine::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const +{ + 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++ ) + { + const auto layoutItem = m_data->qengine.layoutItemAt( i ); + if ( layoutItem->isIgnored() ) + continue; + + const int col = layoutItem->hasUnlimitedSpan( Qt::Horizontal ) + ? layoutItem->firstColumn() + 1 : layoutItem->lastColumn(); + + if ( col > lastColumn ) + lastColumn = col; + + const int row = layoutItem->hasUnlimitedSpan( Qt::Vertical ) + ? layoutItem->firstRow() + 1 : layoutItem->lastRow(); + + if ( row > lastRow ) + lastRow = row; + } + + return QSize( lastColumn + 1, lastRow + 1 ); +} + +void QskGridLayoutEngine::adjustSpans( int numRows, int numColumns ) +{ + for ( int i = 0; i < m_data->qengine.itemCount(); i++ ) + { + auto layoutItem = m_data->qengine.layoutItemAt( i ); + + if ( layoutItem->hasUnlimitedSpan( Qt::Horizontal ) ) + layoutItem->setRowSpan( numColumns - layoutItem->firstColumn(), Qt::Horizontal ); + + if ( layoutItem->hasUnlimitedSpan( Qt::Vertical ) ) + layoutItem->setRowSpan( numRows - layoutItem->firstRow(), Qt::Vertical ); + } +} diff --git a/src/layouts/QskGridLayoutEngine.h b/src/layouts/QskGridLayoutEngine.h new file mode 100644 index 00000000..51cf6530 --- /dev/null +++ b/src/layouts/QskGridLayoutEngine.h @@ -0,0 +1,95 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_GRID_LAYOUT_ENGINE_H +#define QSK_GRID_LAYOUT_ENGINE_H + +#include "QskGlobal.h" + +#include +#include +#include + +class QQuickItem; + +class QskGridLayoutEngine +{ + public: + QskGridLayoutEngine(); + ~QskGridLayoutEngine(); + + void setGeometries( const QRectF ); + void invalidate(); + + void setVisualDirection( Qt::LayoutDirection ); + Qt::LayoutDirection visualDirection() 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 ); + qreal rowSizeHint( int row, Qt::SizeHint ) const; + + void setColumnSizeHint( int column, Qt::SizeHint, qreal width ); + qreal columnSizeHint( int column, Qt::SizeHint ) const; + + QSizeF sizeHint( Qt::SizeHint, const QSizeF& constraint = QSizeF() ) const; + + qreal widthForHeight( qreal height ) const; + qreal heightForWidth( qreal width ) const; + + static qreal defaultSpacing( Qt::Orientation ); + +#if 1 + QSize requiredCells() const; + void adjustSpans( int numRows, int numColumns ); +#endif + + private: + Q_DISABLE_COPY(QskGridLayoutEngine) + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/layouts/QskIndexedLayoutBox.cpp b/src/layouts/QskIndexedLayoutBox.cpp index c37dc9e1..de8119d3 100644 --- a/src/layouts/QskIndexedLayoutBox.cpp +++ b/src/layouts/QskIndexedLayoutBox.cpp @@ -4,8 +4,6 @@ *****************************************************************************/ #include "QskIndexedLayoutBox.h" -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" #include "QskQuick.h" class QskIndexedLayoutBox::PrivateData @@ -14,30 +12,15 @@ class QskIndexedLayoutBox::PrivateData PrivateData() : autoAddChildren( true ) , blockChildAdded( false ) - , defaultAlignment( Qt::AlignLeft | Qt::AlignVCenter ) { } bool autoAddChildren : 1; bool blockChildAdded : 1; - - /* - QGridLayoutEngine is supposed to find the alignment - ( see: QGridLayoutEngine::effectiveAlignment ) by looking up in: - item -> row/column -> layout default. - - Unfortunatly the layout default can't be modified without accessing - private methods and - for some reason worse - QGridLayoutEngine::effectiveAlignment - does not fall back to the layout default for the horizontal alignment. - - But as we don't offer setting the row/column alignment at the public API - of QskIndexedLayoutBox we work around by using them instead. - */ - Qt::Alignment defaultAlignment; }; QskIndexedLayoutBox::QskIndexedLayoutBox( QQuickItem* parent ) - : QskLayoutBox( parent ) + : QskBox( false, parent ) , m_data( new PrivateData() ) { // classBegin/componentComplete -> setActive( false/true ) ? @@ -61,142 +44,6 @@ bool QskIndexedLayoutBox::autoAddChildren() const return m_data->autoAddChildren; } -void QskIndexedLayoutBox::setDefaultAlignment( Qt::Alignment alignment ) -{ - bool hasChanged = false; - - const Qt::Alignment alignV = alignment & Qt::AlignVertical_Mask; - if ( alignV != ( m_data->defaultAlignment & Qt::AlignVertical_Mask ) ) - { - hasChanged = true; - - for ( int row = 0; row < engine().rowCount(); row++ ) - engine().setRowAlignment( row, alignV, Qt::Vertical ); - } - - const Qt::Alignment alignH = alignment & Qt::AlignHorizontal_Mask; - if ( alignH != ( m_data->defaultAlignment & Qt::AlignHorizontal_Mask ) ) - { - hasChanged = true; - - for ( int col = 0; col < engine().columnCount(); col++ ) - engine().setRowAlignment( col, alignH, Qt::Horizontal ); - } - - if ( hasChanged ) - { - m_data->defaultAlignment = alignment; - Q_EMIT defaultAlignmentChanged(); - } -} - -Qt::Alignment QskIndexedLayoutBox::defaultAlignment() const -{ - return m_data->defaultAlignment; -} - -void QskIndexedLayoutBox::addItem( - QQuickItem* item, Qt::Alignment alignment ) -{ - insertItem( -1, item, alignment ); -} - -void QskIndexedLayoutBox::insertItem( - int index, QQuickItem* item, Qt::Alignment alignment ) -{ - if ( item == nullptr ) - return; - - if ( item->parentItem() == this ) - { - const int oldIndex = indexOf( item ); - if ( oldIndex >= 0 ) - { - // the item has been inserted before - - const bool doAppend = index < 0 || index >= itemCount(); - - if ( ( index == oldIndex ) || - ( doAppend && oldIndex == itemCount() - 1 ) ) - { - // already at its position, nothing to do - return; - } - - removeAt( oldIndex ); - } - } - - auto layoutItem = new QskLayoutItem( item, 0, 0 ); - layoutItem->setAlignment( alignment ); - - insertLayoutItem( layoutItem, index ); -} - -void QskIndexedLayoutBox::setAlignment( int index, Qt::Alignment alignment ) -{ - auto layoutItem = engine().layoutItemAt( index ); - if ( layoutItem && ( alignment != layoutItem->alignment() ) ) - { - layoutItem->setAlignment( alignment ); - activate(); // invalidate() ??? - } -} - -Qt::Alignment QskIndexedLayoutBox::alignment( int index ) const -{ - const auto layoutItem = engine().layoutItemAt( index ); - if ( layoutItem ) - return layoutItem->alignment(); - - return Qt::Alignment(); -} - -void QskIndexedLayoutBox::setAlignment( - const QQuickItem* item, Qt::Alignment alignment ) -{ - setAlignment( engine().indexOf( item ), alignment ); -} - -Qt::Alignment QskIndexedLayoutBox::alignment( const QQuickItem* item ) const -{ - return alignment( engine().indexOf( item ) ); -} - -void QskIndexedLayoutBox::insertLayoutItem( - QskLayoutItem* layoutItem, int index ) -{ - const int numItems = itemCount(); - if ( index < 0 || index > numItems ) - index = numItems; - - setupLayoutItem( layoutItem, index ); - - const int rowCount = engine().rowCount(); - const int columnCount = engine().columnCount(); - - // not exception safe !! - m_data->blockChildAdded = true; - insertItemInternal( layoutItem, index ); - m_data->blockChildAdded = false; - - if ( rowCount != engine().rowCount() ) - { - const Qt::Alignment alignV = m_data->defaultAlignment & Qt::AlignVertical_Mask; - for ( int row = 0; row < engine().rowCount(); row++ ) - engine().setRowAlignment( row, alignV, Qt::Vertical ); - } - - if ( columnCount != engine().columnCount() ) - { - const Qt::Alignment alignH = m_data->defaultAlignment & Qt::AlignHorizontal_Mask; - for ( int col = 0; col < engine().columnCount(); col++ ) - engine().setRowAlignment( col, alignH, Qt::Horizontal ); - } - - layoutItemInserted( layoutItem, index ); -} - void QskIndexedLayoutBox::itemChange( QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& value ) { @@ -207,16 +54,27 @@ void QskIndexedLayoutBox::itemChange( if ( m_data->autoAddChildren && !m_data->blockChildAdded ) { if ( !qskIsTransparentForPositioner( value.item ) ) - addItem( value.item ); + autoAddItem( value.item ); } break; } case QQuickItem::ItemChildRemovedChange: { - removeItem( value.item ); + autoRemoveItem( value.item ); break; } +#if 1 + case QQuickItem::ItemSceneChange: + { + // when changing the window we should run into polish anyway + if ( value.window ) + polish(); + + break; + } +#endif + default: { break; @@ -226,4 +84,17 @@ void QskIndexedLayoutBox::itemChange( return Inherited::itemChange( change, value ); } +void QskIndexedLayoutBox::reparentItem( QQuickItem* item ) +{ + if ( item->parent() == nullptr ) + item->setParent( this ); + + if ( item->parentItem() != this ) + { + m_data->blockChildAdded = true; + item->setParentItem( this ); + m_data->blockChildAdded = false; + } +} + #include "moc_QskIndexedLayoutBox.cpp" diff --git a/src/layouts/QskIndexedLayoutBox.h b/src/layouts/QskIndexedLayoutBox.h index 92744414..5ca8eadf 100644 --- a/src/layouts/QskIndexedLayoutBox.h +++ b/src/layouts/QskIndexedLayoutBox.h @@ -6,19 +6,16 @@ #ifndef QSK_INDEXED_LAYOUT_BOX_H #define QSK_INDEXED_LAYOUT_BOX_H -#include "QskLayoutBox.h" +#include "QskBox.h" -class QSK_EXPORT QskIndexedLayoutBox : public QskLayoutBox +class QSK_EXPORT QskIndexedLayoutBox : public QskBox { Q_OBJECT Q_PROPERTY( bool autoAddChildren READ autoAddChildren WRITE setAutoAddChildren NOTIFY autoAddChildrenChanged ) - Q_PROPERTY( Qt::Alignment defaultAlignment READ defaultAlignment - WRITE setDefaultAlignment NOTIFY defaultAlignmentChanged ) - - using Inherited = QskLayoutBox; + using Inherited = QskBox; public: explicit QskIndexedLayoutBox( QQuickItem* parent = nullptr ); @@ -27,28 +24,15 @@ class QSK_EXPORT QskIndexedLayoutBox : public QskLayoutBox void setAutoAddChildren( bool ); bool autoAddChildren() const; - void setDefaultAlignment( Qt::Alignment ); - Qt::Alignment defaultAlignment() const; - - Q_INVOKABLE void addItem( - QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); - - Q_INVOKABLE void insertItem( - int index, QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); - - void setAlignment( int index, Qt::Alignment ); - Qt::Alignment alignment( int index ) const; - - void setAlignment( const QQuickItem*, Qt::Alignment ); - Qt::Alignment alignment( const QQuickItem* ) const; - Q_SIGNALS: void autoAddChildrenChanged(); - void defaultAlignmentChanged(); protected: void itemChange( ItemChange, const ItemChangeData& ) override; - void insertLayoutItem( QskLayoutItem*, int index ); + void reparentItem( QQuickItem* ); + + virtual void autoAddItem( QQuickItem* ) = 0; + virtual void autoRemoveItem( QQuickItem* ) = 0; private: class PrivateData; diff --git a/src/layouts/QskLayoutBox.cpp b/src/layouts/QskLayoutBox.cpp deleted file mode 100644 index 5b59df9c..00000000 --- a/src/layouts/QskLayoutBox.cpp +++ /dev/null @@ -1,403 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskLayoutBox.h" -#include "QskEvent.h" -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" -#include "QskLayoutConstraint.h" - -class QskLayoutBox::PrivateData -{ - public: - PrivateData() - : isActive( true ) - { - } - - bool isActive : 1; - - QskLayoutEngine engine; -}; - -QskLayoutBox::QskLayoutBox( QQuickItem* parent ) - : QskBox( false, parent ) - , m_data( new PrivateData() ) -{ -} - -QskLayoutBox::~QskLayoutBox() -{ - disconnect( this, 0, this, 0 ); // destructor runs on invalidate else - setActive( false ); -} - -void QskLayoutBox::setActive( bool on ) -{ - if ( on == m_data->isActive ) - return; - - m_data->isActive = on; - - for ( int i = 0; i < itemCount(); ++i ) - { - if( auto item = itemAtIndex( i ) ) - setItemActive( item, on ); - } - - if ( on ) - { - resetImplicitSize(); - polish(); - } - - Q_EMIT activeChanged( m_data->isActive ); -} - -bool QskLayoutBox::isActive() const -{ - return m_data->isActive; -} - -int QskLayoutBox::itemCount() const -{ - return m_data->engine.itemCount(); -} - -QQuickItem* QskLayoutBox::itemAtIndex( int index ) const -{ - if ( auto layoutItem = m_data->engine.layoutItemAt( index ) ) - return layoutItem->item(); - - return nullptr; -} - -int QskLayoutBox::indexOf( const QQuickItem* item ) const -{ - if ( item != nullptr ) - return m_data->engine.indexOf( item ); - - return -1; -} - -void QskLayoutBox::insertItemInternal( QskLayoutItem* layoutItem, int index ) -{ - // check if item is already inserted ??? - - auto item = layoutItem->item(); - - if ( index > itemCount() ) - index = -1; // append - - auto& engine = this->engine(); - - if ( item ) - { - if ( item->parent() == nullptr ) - item->setParent( this ); - - if ( item->parentItem() != this ) - item->setParentItem( this ); - - /* - Re-ordering the child items to have a a proper focus tab chain - */ - - bool reordered = false; - - if ( ( index >= 0 ) && ( index < itemCount() - 1 ) ) - { - for ( int i = index; i < engine.itemCount(); i++ ) - { - auto layoutItem = engine.layoutItemAt( i ); - if ( layoutItem && layoutItem->item() ) - { - item->stackBefore( layoutItem->item() ); - reordered = true; - - break; - } - } - } - - if ( !reordered ) - { - const auto children = childItems(); - if ( item != children.last() ) - item->stackAfter( children.last() ); - } - } - - engine.insertLayoutItem( layoutItem, index ); - - if ( m_data->isActive ) - { - setItemActive( item, true ); - - resetImplicitSize(); - polish(); - } -} - -void QskLayoutBox::removeAt( int index ) -{ - auto& engine = this->engine(); - - auto layoutItem = engine.layoutItemAt( index ); - if ( layoutItem == nullptr ) - return; - - setItemActive( layoutItem->item(), false ); - engine.removeItem( layoutItem ); - - layoutItemRemoved( layoutItem, index ); - - delete layoutItem; - - if ( m_data->isActive ) - { - resetImplicitSize(); - polish(); - } -} - -void QskLayoutBox::removeItem( const QQuickItem* item ) -{ - removeAt( indexOf( item ) ); -} - -void QskLayoutBox::clear( bool autoDelete ) -{ - const bool isActive = m_data->isActive; - setActive( false ); - - for ( int i = itemCount() - 1; i >= 0; i-- ) - { - auto item = itemAtIndex( i ); - - removeAt( i ); - - if( item ) - { - if( autoDelete && ( item->parent() == this ) ) - delete item; - else - item->setParentItem( nullptr ); - } - } - - setActive( isActive ); -} - -void QskLayoutBox::setupLayoutItem( QskLayoutItem* layoutItem, int index ) -{ - Q_UNUSED( layoutItem ) - Q_UNUSED( index ) -} - -void QskLayoutBox::layoutItemInserted( QskLayoutItem* layoutItem, int index ) -{ - Q_UNUSED( layoutItem ) - Q_UNUSED( index ) -} - -void QskLayoutBox::layoutItemRemoved( QskLayoutItem* layoutItem, int index ) -{ - Q_UNUSED( layoutItem ) - Q_UNUSED( index ) -} - -void QskLayoutBox::activate() -{ - if ( m_data->isActive ) - polish(); -} - -void QskLayoutBox::invalidate() -{ - engine().invalidate(); - activate(); - - resetImplicitSize(); -} - -void QskLayoutBox::adjustItem( const QQuickItem* item ) -{ - adjustItemAt( indexOf( item ) ); -} - -void QskLayoutBox::adjustItemAt( int index ) -{ - QskLayoutItem* layoutItem = engine().layoutItemAt( index ); - if ( layoutItem == nullptr ) - return; - - // setting UpdateNone to all others ??? - layoutItem->setUpdateMode( QskLayoutItem::UpdateAlways ); - engine().setGeometries( alignedLayoutRect( layoutRect() ) ); - layoutItem->setUpdateMode( QskLayoutItem::UpdateWhenVisible ); -} - -void QskLayoutBox::updateLayout() -{ - if ( m_data->isActive ) - engine().setGeometries( alignedLayoutRect( layoutRect() ) ); -} - -QRectF QskLayoutBox::alignedLayoutRect( const QRectF& rect ) const -{ - return rect; -} - -QSizeF QskLayoutBox::contentsSizeHint() const -{ - if ( !isActive() ) - return QSizeF( -1, -1 ); - - if ( itemCount() == 0 ) - return QSizeF( 0, 0 ); - - return layoutItemsSizeHint(); -} - -QSizeF QskLayoutBox::layoutItemsSizeHint() const -{ - return engine().sizeHint( Qt::PreferredSize, QSizeF() ); -} - -qreal QskLayoutBox::heightForWidth( qreal width ) const -{ - auto constrainedHeight = - [this]( QskLayoutConstraint::Type, const QskControl*, qreal width ) - { - return engine().heightForWidth( width ); - }; - - return QskLayoutConstraint::constrainedMetric( - QskLayoutConstraint::HeightForWidth, this, width, constrainedHeight ); -} - -qreal QskLayoutBox::widthForHeight( qreal height ) const -{ - auto constrainedWidth = - [this]( QskLayoutConstraint::Type, const QskControl*, qreal height ) - { - return engine().widthForHeight( height ); - }; - - return QskLayoutConstraint::constrainedMetric( - QskLayoutConstraint::WidthForHeight, this, height, constrainedWidth ); -} - -void QskLayoutBox::geometryChangeEvent( QskGeometryChangeEvent* event ) -{ - Inherited::geometryChangeEvent( event ); - - if ( event->isResized() ) - activate(); -} - -void QskLayoutBox::setItemActive( const QQuickItem* item, bool on ) -{ - if ( item == nullptr ) - return; - - // QskControl sends QEvent::LayoutRequest - - const bool hasLayoutRequests = qskControlCast( item ); - if ( !hasLayoutRequests ) - { - if ( on ) - { - connect( item, &QQuickItem::implicitWidthChanged, - this, &QskLayoutBox::invalidate ); - - connect( item, &QQuickItem::implicitHeightChanged, - this, &QskLayoutBox::invalidate ); - } - else - { - disconnect( item, &QQuickItem::implicitWidthChanged, - this, &QskLayoutBox::invalidate ); - - disconnect( item, &QQuickItem::implicitHeightChanged, - this, &QskLayoutBox::invalidate ); - } - } - - if ( on ) - connect( item, &QQuickItem::visibleChanged, this, &QskLayoutBox::activate ); - else - disconnect( item, &QQuickItem::visibleChanged, this, &QskLayoutBox::activate ); -} - -QskLayoutEngine& QskLayoutBox::engine() -{ - return m_data->engine; -} - -const QskLayoutEngine& QskLayoutBox::engine() const -{ - return m_data->engine; -} - -void QskLayoutBox::itemChange( ItemChange change, const ItemChangeData& value ) -{ - Inherited::itemChange( change, value ); - - switch ( change ) - { - case ItemChildRemovedChange: - { - removeItem( value.item ); - break; - } - case QQuickItem::ItemVisibleHasChanged: - { - if ( value.boolValue ) - activate(); - break; - } - case QQuickItem::ItemSceneChange: - { - if ( value.window ) - activate(); - break; - } - default: - break; - } -} - -bool QskLayoutBox::event( QEvent* event ) -{ - switch ( event->type() ) - { - case QEvent::LayoutRequest: - { - invalidate(); - break; - } - case QEvent::LayoutDirectionChange: - { - m_data->engine.setVisualDirection( - layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight ); - - activate(); - break; - } - case QEvent::ContentsRectChange: - { - activate(); - break; - } - default: - break; - } - - return Inherited::event( event ); -} - -#include "moc_QskLayoutBox.cpp" diff --git a/src/layouts/QskLayoutBox.h b/src/layouts/QskLayoutBox.h deleted file mode 100644 index 1e68d6dd..00000000 --- a/src/layouts/QskLayoutBox.h +++ /dev/null @@ -1,90 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#ifndef QSK_LAYOUT_BOX_H -#define QSK_LAYOUT_BOX_H - -#include "QskBox.h" - -class QskLayoutEngine; -class QskLayoutItem; - -class QSK_EXPORT QskLayoutBox : public QskBox -{ - Q_OBJECT - - // signals ??? - Q_PROPERTY( int itemCount READ itemCount() ) - Q_PROPERTY( bool empty READ isEmpty() ) - - Q_PROPERTY( bool active READ isActive - WRITE setActive NOTIFY activeChanged ) - - using Inherited = QskBox; - - public: - explicit QskLayoutBox( QQuickItem* parent = nullptr ); - ~QskLayoutBox() override; - - bool isEmpty() const; - - int itemCount() const; - QQuickItem* itemAtIndex( int index ) const; - int indexOf( const QQuickItem* ) const; - - void removeItem( const QQuickItem* ); - void removeAt( int index ); - - void setActive( bool ); - bool isActive() const; - - void adjustItem( const QQuickItem* ); - void adjustItemAt( int index ); - - QSizeF contentsSizeHint() const override; - - qreal heightForWidth( qreal width ) const override; - qreal widthForHeight( qreal height ) const override; - - Q_SIGNALS: - void activeChanged( bool ); - - public Q_SLOTS: - void activate(); - void invalidate(); - void clear( bool autoDelete = false ); - - protected: - bool event( QEvent* ) override; - void geometryChangeEvent( QskGeometryChangeEvent* ) override; - - void itemChange( ItemChange, const ItemChangeData& ) override; - void updateLayout() override; - - QskLayoutEngine& engine(); - const QskLayoutEngine& engine() const; - - void setItemActive( const QQuickItem*, bool on ); - - void insertItemInternal( QskLayoutItem*, int index ); - - virtual void setupLayoutItem( QskLayoutItem*, int index ); - virtual void layoutItemInserted( QskLayoutItem*, int index ); - virtual void layoutItemRemoved( QskLayoutItem*, int index ); - - virtual QRectF alignedLayoutRect( const QRectF& ) const; - virtual QSizeF layoutItemsSizeHint() const; - - private: - class PrivateData; - std::unique_ptr< PrivateData > m_data; -}; - -inline bool QskLayoutBox::isEmpty() const -{ - return itemCount() <= 0; -} - -#endif diff --git a/src/layouts/QskLayoutConstraint.cpp b/src/layouts/QskLayoutConstraint.cpp index d365a20d..103de00c 100644 --- a/src/layouts/QskLayoutConstraint.cpp +++ b/src/layouts/QskLayoutConstraint.cpp @@ -103,10 +103,10 @@ QskLayoutConstraint::Type QskLayoutConstraint::constraintType( const QQuickItem* Type constraintType = Unconstrained; if ( auto control = qskControlCast( item ) ) - { + { const auto policy = control->sizePolicy(); if ( policy.horizontalPolicy() == QskSizePolicy::Constrained ) - { + { constraintType = WidthForHeight; } else if ( policy.verticalPolicy() == QskSizePolicy::Constrained ) @@ -125,7 +125,7 @@ QskLayoutConstraint::Type QskLayoutConstraint::constraintType( const QQuickItem* constraintType = HeightForWidth; } } - + return constraintType; } @@ -366,7 +366,7 @@ qreal QskLayoutConstraint::sizeHint( const QQuickItem* item, QSizeF QskLayoutConstraint::sizeHint( const QQuickItem* item, Qt::SizeHint whichHint, const QSizeF& constraint ) { - if ( item == nullptr || whichHint < Qt::MinimumSize || whichHint > Qt::MaximumSize ) + if ( item == nullptr || whichHint < Qt::MinimumSize || whichHint > Qt::MaximumSize ) return QSizeF( 0, 0 ); QSizeF hint( 0, 0 ); @@ -438,7 +438,7 @@ QRectF QskLayoutConstraint::itemRect( const QQuickItem* item, switch( constraintType( item ) ) { case HeightForWidth: - { + { if ( size.width() > rect.width() ) size = qskExpandedSize( item, QSizeF( rect.width(), -1 ) ); diff --git a/src/layouts/QskLayoutEngine.cpp b/src/layouts/QskLayoutEngine.cpp deleted file mode 100644 index 9aca315f..00000000 --- a/src/layouts/QskLayoutEngine.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" - -static inline bool qskIsColliding( - const QskLayoutEngine* engine, QskLayoutItem* item ) -{ - for ( int row = item->firstRow(); row <= item->lastRow(); row++ ) - { - for ( int col = item->firstColumn(); col <= item->lastColumn(); col++ ) - { - if ( engine->itemAt( row, col ) ) - return true; - } - } - - return false; -} - -namespace -{ - class MessageHandler - { - public: - MessageHandler() - { - m_defaultHandler = qInstallMessageHandler( suppressWarning ); - } - - ~MessageHandler() - { - qInstallMessageHandler( m_defaultHandler ); - } - - private: - static void suppressWarning( QtMsgType type, - const QMessageLogContext& context, const QString& message ) - { - if ( type == QtWarningMsg ) - return; - - if ( m_defaultHandler ) - ( *m_defaultHandler )( type, context, message ); - } - - static QtMessageHandler m_defaultHandler; - }; - - QtMessageHandler MessageHandler::m_defaultHandler; - - class LayoutStyleInfo final : public QAbstractLayoutStyleInfo - { - public: - LayoutStyleInfo() - { - } - - qreal spacing( Qt::Orientation ) const override - { - // 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 - } - }; -} - -QskLayoutEngine::QskLayoutEngine() - : QGridLayoutEngine( Qt::AlignVCenter, false /*snapToPixelGrid*/ ) -{ - /* - 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. - */ -} - -QskLayoutEngine::~QskLayoutEngine() -{ -} - -void QskLayoutEngine::setGeometries( const QRectF rect ) -{ - const LayoutStyleInfo styleInfo; - QGridLayoutEngine::setGeometries( rect, &styleInfo ); -} - -void QskLayoutEngine::insertLayoutItem( QskLayoutItem* item, int index ) -{ - if ( qskIsColliding( this, item ) ) - { - // It is totally valid to have more than one item in the same cell - // and we make use of it f.e in QskStackLayout. So we better - // suppress the corresponding warning to avoid confusion. - - MessageHandler suppressWarning; - insertItem( item, index ); - } - else - { - insertItem( item, index ); - } -} - -int QskLayoutEngine::indexAt( int row, int column ) const -{ - const auto item = layoutItemAt( row, column ); - if ( item ) - return q_items.indexOf( item ); - - return -1; -} - -QskLayoutItem* QskLayoutEngine::layoutItemAt( int index ) const -{ - if ( index < 0 || index >= q_items.count() ) - return nullptr; - - return static_cast< QskLayoutItem* >( q_items[ index ] ); -} - -QskLayoutItem* QskLayoutEngine::layoutItemAt( int row, int column ) const -{ - if ( row < 0 || row >= rowCount() || column < 0 || column >= columnCount() ) - return nullptr; - - return static_cast< QskLayoutItem* >( itemAt( row, column ) ); -} - -QskLayoutItem* QskLayoutEngine::layoutItemAt( - int row, int column, Qt::Orientation orientation ) const -{ - if ( orientation == Qt::Horizontal ) - return layoutItemAt( row, column ); - else - return layoutItemAt( column, row ); -} - -int QskLayoutEngine::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 ) - { - const auto layoutItem = static_cast< const QskLayoutItem* >( q_items[ i ] ); - if ( layoutItem->item() == item ) - return i; - } - - return -1; -} - -QSizeF QskLayoutEngine::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const -{ - const LayoutStyleInfo styleInfo; - return QGridLayoutEngine::sizeHint( which, constraint, &styleInfo ); -} - -qreal QskLayoutEngine::widthForHeight( qreal height ) const -{ - const QSizeF constraint( -1, height ); - return sizeHint( Qt::PreferredSize, constraint ).width(); -} - -qreal QskLayoutEngine::heightForWidth( qreal width ) const -{ - const QSizeF constraint( width, -1 ); - return sizeHint( Qt::PreferredSize, constraint ).height(); -} - -qreal QskLayoutEngine::spacing( Qt::Orientation orientation ) const -{ - const LayoutStyleInfo styleInfo; - return QGridLayoutEngine::spacing( orientation, &styleInfo ); -} - -qreal QskLayoutEngine::defaultSpacing( Qt::Orientation orientation ) -{ - return LayoutStyleInfo().spacing( orientation ); -} - -#if 1 -// QGridLayout or here ??? -QSize QskLayoutEngine::requiredCells() const -{ - int lastRow = -1; - int lastColumn = -1; - - for ( int i = 0; i < itemCount(); i++ ) - { - const auto layoutItem = layoutItemAt( i ); - if ( layoutItem->isIgnored() ) - continue; - - const int col = layoutItem->hasUnlimitedSpan( Qt::Horizontal ) - ? layoutItem->firstColumn() + 1 : layoutItem->lastColumn(); - - if ( col > lastColumn ) - lastColumn = col; - - const int row = layoutItem->hasUnlimitedSpan( Qt::Vertical ) - ? layoutItem->firstRow() + 1 : layoutItem->lastRow(); - - if ( row > lastRow ) - lastRow = row; - } - - return QSize( lastColumn + 1, lastRow + 1 ); -} - -void QskLayoutEngine::adjustSpans( int numRows, int numColumns ) -{ - for ( int i = 0; i < itemCount(); i++ ) - { - auto layoutItem = layoutItemAt( i ); - - if ( layoutItem->hasUnlimitedSpan( Qt::Horizontal ) ) - layoutItem->setRowSpan( numColumns - layoutItem->firstColumn(), Qt::Horizontal ); - - if ( layoutItem->hasUnlimitedSpan( Qt::Vertical ) ) - layoutItem->setRowSpan( numRows - layoutItem->firstRow(), Qt::Vertical ); - } -} - -#endif diff --git a/src/layouts/QskLayoutEngine.h b/src/layouts/QskLayoutEngine.h deleted file mode 100644 index 257d44e6..00000000 --- a/src/layouts/QskLayoutEngine.h +++ /dev/null @@ -1,61 +0,0 @@ -/****************************************************************************** - * 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_H -#define QSK_LAYOUT_ENGINE_H - -#include "QskGlobal.h" - -QSK_QT_PRIVATE_BEGIN -/* - QskLayoutEngine.h should be hidden into some cpp file - as it needs private headers. TODO - */ - -#include -QSK_QT_PRIVATE_END - -class QskLayoutItem; -class QQuickItem; - -class QskLayoutEngine : public QGridLayoutEngine -{ - public: - QskLayoutEngine(); - ~QskLayoutEngine(); - - void setGeometries( const QRectF ); - - void insertLayoutItem( QskLayoutItem* item, int index ); - - QskLayoutItem* layoutItemAt( int index ) const; - QskLayoutItem* layoutItemAt( int row, int column ) const; - QskLayoutItem* layoutItemAt( int row, int column, Qt::Orientation ) const; - - int indexAt( int row, int column ) const; - - QskLayoutItem* layoutItemOf( const QQuickItem* ) const; - int indexOf( const QQuickItem* item ) const; - - QSizeF sizeHint( Qt::SizeHint, const QSizeF& constraint = QSizeF() ) const; - - qreal widthForHeight( qreal height ) const; - qreal heightForWidth( qreal width ) const; - - qreal spacing( Qt::Orientation ) const; - static qreal defaultSpacing( Qt::Orientation ); - -#if 1 - QSize requiredCells() const; - void adjustSpans( int numRows, int numColumns ); -#endif -}; - -inline QskLayoutItem* QskLayoutEngine::layoutItemOf( const QQuickItem* item ) const -{ - return layoutItemAt( indexOf( item ) ); -} - -#endif diff --git a/src/layouts/QskLayoutItem.cpp b/src/layouts/QskLayoutItem.cpp deleted file mode 100644 index 72acc6a2..00000000 --- a/src/layouts/QskLayoutItem.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskLayoutItem.h" -#include "QskControl.h" -#include "QskLayoutConstraint.h" -#include "QskQuick.h" - -QskLayoutItem::QskLayoutItem( QQuickItem* item, int row, int column, int rowSpan, int columnSpan ) - : Inherited( row, column, qMax( rowSpan, 1 ), qMax( columnSpan, 1 ), Qt::Alignment() ) - , m_item( item ) - , m_isGeometryDirty( false ) - , m_isStretchable( false ) - , m_retainSizeWhenHidden( false ) - , m_unlimitedRowSpan( rowSpan <= 0 ) - , m_unlimitedColumnSpan( columnSpan <= 0 ) - , m_updateMode( UpdateWhenVisible ) -{ -} - -QskLayoutItem::QskLayoutItem( const QSizeF& size, int stretch, int row, int column ) - : Inherited( row, column, 1, 1, Qt::Alignment() ) - , m_item( nullptr ) - , m_spacingHint( size ) - , m_isGeometryDirty( false ) - , m_isStretchable( stretch > 0 ) - , m_retainSizeWhenHidden( false ) - , m_unlimitedRowSpan( false ) - , m_unlimitedColumnSpan( false ) - , m_updateMode( UpdateWhenVisible ) -{ -} - -QskLayoutItem::~QskLayoutItem() -{ -} - -QskLayoutItem::UpdateMode QskLayoutItem::updateMode() const -{ - return m_updateMode; -} - -void QskLayoutItem::setUpdateMode( UpdateMode mode ) -{ - m_updateMode = mode; -} - -bool QskLayoutItem::retainSizeWhenHidden() const -{ - return m_retainSizeWhenHidden; -} - -void QskLayoutItem::setRetainSizeWhenHidden( bool on ) -{ - m_retainSizeWhenHidden = on; -} - -QSizeF QskLayoutItem::spacingHint() const -{ - return m_spacingHint; -} - -void QskLayoutItem::setSpacingHint( const QSizeF& hint ) -{ - m_spacingHint = hint; -} - -QSizeF QskLayoutItem::sizeHint( - Qt::SizeHint whichHint, const QSizeF& constraint ) const -{ - if ( m_item == nullptr ) - { - if ( whichHint < Qt::MinimumSize || whichHint > Qt::MaximumSize ) - return QSizeF( 0, 0 ); - - // a spacer item - if ( whichHint == Qt::MaximumSize ) - { - if ( m_isStretchable ) - return QSizeF( QskLayoutConstraint::unlimited, QskLayoutConstraint::unlimited ); - - if ( m_spacingHint.width() < 0 ) - return QSizeF( QskLayoutConstraint::unlimited, m_spacingHint.height() ); - else - return QSizeF( m_spacingHint.width(), QskLayoutConstraint::unlimited ); - } - else - { - if ( m_spacingHint.width() < 0 ) - return QSizeF( 0, m_spacingHint.height() ); - else - return QSizeF( m_spacingHint.width(), 0 ); - } - } - else - { - return QskLayoutConstraint::sizeHint( m_item, whichHint, constraint ); - } -} - -QLayoutPolicy::Policy QskLayoutItem::sizePolicy( Qt::Orientation orientation ) const -{ - auto policy = QskLayoutConstraint::sizePolicy( m_item ).policy( orientation ); - -#if 0 - if ( ( policy == QskSizePolicy::Preferred ) && m_item ) - { - // QskSizePolicy::Preferred without having a preferred size is the default - // setting of QskControl - taken from what QWidget does - but this combination - // doesn't make much sense. Usually every derived control is supposed - // to set specific values, but in case it has been forgotten we better - // ignore the preferred size then. - - const QSizeF hint = QskLayoutConstraint::effectiveConstraint( m_item, Qt::PreferredSize ); - - const qreal value = ( orientation == Qt::Horizontal ) ? hint.width() : hint.height(); - if ( value <= 0 ) - policy = QskSizePolicy::Ignored; - } -#endif - - return static_cast< QLayoutPolicy::Policy >( policy ); -} - -void QskLayoutItem::setGeometry( const QRectF& rect ) -{ - if ( m_item == nullptr ) - return; - - if ( m_updateMode == UpdateNone ) - { - if ( !m_isGeometryDirty ) - m_isGeometryDirty = ( rect != qskItemGeometry( m_item ) ); - - return; - } - - if ( m_updateMode == UpdateWhenVisible ) - { - if ( !m_item->isVisible() ) - return; - } - - m_isGeometryDirty = false; - qskSetItemGeometry( m_item, rect ); -} - -bool QskLayoutItem::hasDynamicConstraint() const -{ - if ( m_item ) - { - using namespace QskLayoutConstraint; - return constraintType( m_item ) != Unconstrained; - } - - return false; -} - -Qt::Orientation QskLayoutItem::dynamicConstraintOrientation() const -{ - Qt::Orientation orientation = Qt::Vertical; - - if ( auto control = qskControlCast( m_item ) ) - { - const auto policy = control->sizePolicy().horizontalPolicy(); - - return ( policy == QskSizePolicy::Constrained ) - ? Qt::Horizontal : Qt::Vertical; - } - - return orientation; -} - -bool QskLayoutItem::isIgnored() const -{ - if ( m_item && !qskIsVisibleToParent( m_item ) ) - return !m_retainSizeWhenHidden; - - return false; -} - -QLayoutPolicy::ControlTypes QskLayoutItem::controlTypes( LayoutSide side ) const -{ - return Inherited::controlTypes( side ); -} diff --git a/src/layouts/QskLayoutItem.h b/src/layouts/QskLayoutItem.h deleted file mode 100644 index 0f37a9b4..00000000 --- a/src/layouts/QskLayoutItem.h +++ /dev/null @@ -1,106 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#ifndef QSK_LAYOUT_ITEM_H -#define QSK_LAYOUT_ITEM_H - -#include "QskGlobal.h" - -QSK_QT_PRIVATE_BEGIN -/* - QskLayoutItem.h should be hidden into some cpp file - as it needs private headers. TODO - */ - -#include -QSK_QT_PRIVATE_END - -class QQuickItem; - -class QskLayoutItem : public QGridLayoutItem -{ - using Inherited = QGridLayoutItem; - - public: - enum UpdateMode - { - UpdateNone, - UpdateWhenVisible, - UpdateAlways - }; - - QskLayoutItem( QQuickItem* item, int row, int column, - int rowSpan = 1, int columnSpan = 1 ); - - QskLayoutItem( const QSizeF& size, int stretch, int row, int column ); - - ~QskLayoutItem() override; - - QQuickItem* item(); - const QQuickItem* item() const; - - QLayoutPolicy::Policy sizePolicy( Qt::Orientation ) const override final; - QSizeF sizeHint( Qt::SizeHint, const QSizeF& ) const override final; - void setGeometry( const QRectF& ) override final; - - bool hasDynamicConstraint() const override final; - Qt::Orientation dynamicConstraintOrientation() const override final; - - bool isIgnored() const override final; - QLayoutPolicy::ControlTypes controlTypes( LayoutSide side ) const override final; - - bool retainSizeWhenHidden() const; - void setRetainSizeWhenHidden( bool on ); - - UpdateMode updateMode() const; - void setUpdateMode( UpdateMode ); - - QSizeF spacingHint() const; - void setSpacingHint( const QSizeF& ); - - bool isGeometryDirty() const; - - bool hasUnlimitedSpan() const; - bool hasUnlimitedSpan( Qt::Orientation orientation ) const; - - private: - QQuickItem* m_item; - QSizeF m_spacingHint; - - bool m_isGeometryDirty : 1; - bool m_isStretchable : 1; - bool m_retainSizeWhenHidden : 1; - bool m_unlimitedRowSpan : 1; - bool m_unlimitedColumnSpan : 1; - UpdateMode m_updateMode : 2; -}; - -inline QQuickItem* QskLayoutItem::item() -{ - return m_item; -} - -inline const QQuickItem* QskLayoutItem::item() const -{ - return m_item; -} - -inline bool QskLayoutItem::isGeometryDirty() const -{ - return m_isGeometryDirty; -} - -inline bool QskLayoutItem::hasUnlimitedSpan( Qt::Orientation orientation ) const -{ - return ( orientation == Qt::Horizontal ) - ? m_unlimitedColumnSpan : m_unlimitedRowSpan; -} - -inline bool QskLayoutItem::hasUnlimitedSpan() const -{ - return m_unlimitedColumnSpan || m_unlimitedRowSpan; -} - -#endif diff --git a/src/layouts/QskLinearBox.cpp b/src/layouts/QskLinearBox.cpp index 8f6dd453..a04dd137 100644 --- a/src/layouts/QskLinearBox.cpp +++ b/src/layouts/QskLinearBox.cpp @@ -4,26 +4,57 @@ *****************************************************************************/ #include "QskLinearBox.h" -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" +#include "QskLinearLayoutEngine.h" -#include +#include "QskLayoutConstraint.h" +#include "QskEvent.h" +#include "QskQuick.h" + +static void qskSetItemActive( QObject* receiver, const QQuickItem* item, bool on ) +{ + if ( ( item == nullptr ) || ( qskControlCast( item ) != nullptr ) ) + return; + + /* + For QQuickItems not being derived from QskControl we manually + send QEvent::LayoutRequest events. + */ + + if ( on ) + { + auto sendLayoutRequest = + [receiver]() + { + QEvent event( QEvent::LayoutRequest ); + QCoreApplication::sendEvent( receiver, &event ); + }; + + QObject::connect( item, &QQuickItem::implicitWidthChanged, + receiver, sendLayoutRequest ); + + QObject::connect( item, &QQuickItem::implicitHeightChanged, + receiver, sendLayoutRequest ); + + QObject::connect( item, &QQuickItem::visibleChanged, + receiver, sendLayoutRequest ); + } + else + { + QObject::disconnect( item, &QQuickItem::implicitWidthChanged, receiver, nullptr ); + QObject::disconnect( item, &QQuickItem::implicitHeightChanged, receiver, nullptr ); + QObject::disconnect( item, &QQuickItem::visibleChanged, receiver, nullptr ); + } +} class QskLinearBox::PrivateData { public: - PrivateData( Qt::Orientation orient, uint dim ) - : dimension( dim ) - , orientation( orient ) - , transposeAlignments( false ) + PrivateData( Qt::Orientation orientation, uint dimension ) + : engine( orientation, dimension ) { } - uint dimension; - Qt::Edges extraSpacingAt; - - Qt::Orientation orientation : 2; - bool transposeAlignments : 1; + QskLinearLayoutEngine engine; }; QskLinearBox::QskLinearBox( QQuickItem* parent ) @@ -36,9 +67,8 @@ QskLinearBox::QskLinearBox( Qt::Orientation orientation, QQuickItem* parent ) { } -QskLinearBox::QskLinearBox( - Qt::Orientation orientation, uint dimension, QQuickItem* parent ) - : Inherited( parent ) +QskLinearBox::QskLinearBox( Qt::Orientation orientation, uint dimension, QQuickItem* parent ) + : QskIndexedLayoutBox( parent ) , m_data( new PrivateData( orientation, dimension ) ) { } @@ -47,97 +77,308 @@ QskLinearBox::~QskLinearBox() { } +int QskLinearBox::entryCount() const +{ + return m_data->engine.count(); +} + +QQuickItem* QskLinearBox::itemAtIndex( int index ) const +{ + return m_data->engine.itemAt( index ); +} + +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; +} + +void QskLinearBox::removeAt( int index ) +{ + removeItemInternal( index, false ); +} + +void QskLinearBox::removeItemInternal( int index, bool unparent ) +{ + auto& engine = m_data->engine; + + if ( index < 0 || index >= engine.count() ) + return; + + auto item = engine.itemAt( index ); + engine.removeAt( index ); + + if ( item ) + { + qskSetItemActive( this, engine.itemAt( index ), false ); + + if ( !unparent ) + { + if ( item->parentItem() == this ) + item->setParentItem( nullptr ); + } + } + + resetImplicitSize(); + polish(); +} + +void QskLinearBox::removeItem( const QQuickItem* item ) +{ + removeAt( indexOf( item ) ); +} + +void QskLinearBox::clear( bool autoDelete ) +{ + auto& engine = m_data->engine; + + // do we have visible entries + const bool hasVisibleEntries = engine.rowCount() > 0; + + for ( int i = engine.count() - 1; i >= 0; i-- ) + { + auto item = engine.itemAt( i ); + engine.removeAt( i ); + + if( item ) + { + qskSetItemActive( this, item, false ); + + if( autoDelete && ( item->parent() == this ) ) + delete item; + else + item->setParentItem( nullptr ); + } + } + + if ( hasVisibleEntries ) + resetImplicitSize(); +} + +void QskLinearBox::autoAddItem( QQuickItem* item ) +{ + insertItem( -1, item ); +} + +void QskLinearBox::autoRemoveItem( QQuickItem* item ) +{ + removeItemInternal( indexOf( item ), true ); +} + +void QskLinearBox::activate() +{ + polish(); +} + +void QskLinearBox::invalidate() +{ + m_data->engine.invalidate(); + + resetImplicitSize(); + polish(); +} + +void QskLinearBox::updateLayout() +{ + m_data->engine.setGeometries( layoutRect() ); +} + +QSizeF QskLinearBox::contentsSizeHint() const +{ + return m_data->engine.sizeHint( Qt::PreferredSize, QSizeF() ); +} + +qreal QskLinearBox::heightForWidth( qreal width ) const +{ + auto constrainedHeight = + [this]( QskLayoutConstraint::Type, const QskControl*, qreal width ) + { + return m_data->engine.heightForWidth( width ); + }; + + return QskLayoutConstraint::constrainedMetric( + QskLayoutConstraint::HeightForWidth, this, width, constrainedHeight ); +} + +qreal QskLinearBox::widthForHeight( qreal height ) const +{ + auto constrainedWidth = + [this]( QskLayoutConstraint::Type, const QskControl*, qreal height ) + { + return m_data->engine.widthForHeight( height ); + }; + + return QskLayoutConstraint::constrainedMetric( + QskLayoutConstraint::WidthForHeight, this, height, constrainedWidth ); +} + +void QskLinearBox::geometryChangeEvent( QskGeometryChangeEvent* event ) +{ + Inherited::geometryChangeEvent( event ); + + if ( event->isResized() ) + polish(); +} + + +void QskLinearBox::itemChange( ItemChange change, const ItemChangeData& value ) +{ + Inherited::itemChange( change, value ); + +#if 1 + if ( change == QQuickItem::ItemVisibleHasChanged ) + { + // when becoming visible we should run into polish anyway + if ( value.boolValue ) + polish(); + } +#endif +} + +bool QskLinearBox::event( QEvent* event ) +{ + switch ( event->type() ) + { + case QEvent::LayoutRequest: + { + invalidate(); + break; + } + case QEvent::LayoutDirectionChange: + { + m_data->engine.setVisualDirection( + layoutMirroring() ? Qt::RightToLeft : Qt::LeftToRight ); + + polish(); + break; + } + case QEvent::ContentsRectChange: + { + polish(); + break; + } + default: + break; + } + + return Inherited::event( event ); +} + void QskLinearBox::setDimension( uint dimension ) { if ( dimension < 1 ) dimension = 1; - if ( dimension != m_data->dimension ) - { - m_data->dimension = dimension; + auto& engine = m_data->engine; + + if ( dimension != engine.dimension() ) + { + engine.setDimension( dimension ); - rearrange(); polish(); + resetImplicitSize(); + + Q_EMIT dimensionChanged(); } } uint QskLinearBox::dimension() const { - return m_data->dimension; + return m_data->engine.dimension(); } void QskLinearBox::setOrientation( Qt::Orientation orientation ) { - if ( m_data->orientation != orientation ) - transpose(); + auto& engine = m_data->engine; + + if ( engine.orientation() != orientation ) + { + engine.setOrientation( orientation ); + + polish(); + resetImplicitSize(); + + Q_EMIT orientationChanged(); + } } Qt::Orientation QskLinearBox::orientation() const { - return m_data->orientation; + return m_data->engine.orientation(); } void QskLinearBox::transpose() { - const Qt::Orientation orientation = - ( m_data->orientation == Qt::Horizontal ) ? Qt::Vertical : Qt::Horizontal; + auto& engine = m_data->engine; - const int numItems = itemCount(); +#if 0 + #include - if ( numItems > 0 ) + for ( int i = 0; i < engine.itemCount(); i++ ) { - for ( int i = 0; i < numItems; i++ ) - { - QskLayoutItem* layoutItem = engine().layoutItemAt( i ); - - const int row = layoutItem->firstColumn(); - const int col = layoutItem->firstRow(); - - engine().removeItem( layoutItem ); - - layoutItem->setFirstRow( row, Qt::Vertical ); - layoutItem->setFirstRow( col, Qt::Horizontal ); - -#if 1 - if ( m_data->transposeAlignments ) - { - // Is it worth to blow the API with this flag, or would - // it be even better to have an indvidual flag for each - // item - and what about the size policies: do we want to - // transpose them too ? - - const auto alignment = static_cast< Qt::Alignment >( - qbswap( static_cast< quint16 >( layoutItem->alignment() ) ) ); - - layoutItem->setAlignment( alignment ); - } -#endif - - if ( layoutItem->item() == nullptr ) - { - // a spacing or stretch - layoutItem->setSpacingHint( - layoutItem->spacingHint().transposed() ); - } - - engine().insertLayoutItem( layoutItem, i ); - } - - invalidate(); + auto alignment = engine.alignmentAt( i ); + qbswap( static_cast< quint16 >( alignment ) ); + engine.setAlignmentAt( i, alignment ); } - m_data->orientation = orientation; - Q_EMIT orientationChanged(); + // extraSpacingAt ??? +#endif + + if ( engine.orientation() == Qt::Horizontal ) + setOrientation( Qt::Vertical ); + else + setOrientation( Qt::Horizontal ); +} + +void QskLinearBox::setDefaultAlignment( Qt::Alignment alignment ) +{ + auto& engine = m_data->engine; + + if ( alignment != engine.defaultAlignment() ) + { + engine.setDefaultAlignment( alignment ); + Q_EMIT defaultAlignmentChanged(); + } +} + +Qt::Alignment QskLinearBox::defaultAlignment() const +{ + return m_data->engine.defaultAlignment(); } void QskLinearBox::setSpacing( qreal spacing ) { + /* + we should have setSpacing( qreal, Qt::Orientations ), + but need to create an API for Qml in QskQml + using qmlAttachedPropertiesObject then. TODO ... + */ spacing = qMax( spacing, 0.0 ); - if ( spacing != engine().spacing( Qt::Horizontal ) ) + auto& engine = m_data->engine; + + if ( spacing != engine.spacing( Qt::Horizontal ) ) { - engine().setSpacing( spacing, Qt::Horizontal | Qt::Vertical ); - activate(); + engine.setSpacing( spacing, Qt::Horizontal | Qt::Vertical ); + polish(); Q_EMIT spacingChanged(); } @@ -145,22 +386,22 @@ void QskLinearBox::setSpacing( qreal spacing ) void QskLinearBox::resetSpacing() { - const qreal spacing = QskLayoutEngine::defaultSpacing( Qt::Horizontal ); + const qreal spacing = m_data->engine.defaultSpacing( Qt::Horizontal ); setSpacing( spacing ); } qreal QskLinearBox::spacing() const { // do we always want to have the same spacing for both orientations - return engine().spacing( Qt::Horizontal ); + return m_data->engine.spacing( Qt::Horizontal ); } void QskLinearBox::setExtraSpacingAt( Qt::Edges edges ) { - if ( edges != m_data->extraSpacingAt ) + if ( edges != m_data->engine.extraSpacingAt() ) { - m_data->extraSpacingAt = edges; - activate(); + m_data->engine.setExtraSpacingAt( edges ); + polish(); Q_EMIT extraSpacingAtChanged(); } @@ -168,7 +409,84 @@ void QskLinearBox::setExtraSpacingAt( Qt::Edges edges ) Qt::Edges QskLinearBox::extraSpacingAt() const { - return m_data->extraSpacingAt; + return m_data->engine.extraSpacingAt(); +} + +void QskLinearBox::addItem( QQuickItem* item, Qt::Alignment alignment ) +{ + insertItem( -1, item, alignment ); +} + +void QskLinearBox::insertItem( + int index, QQuickItem* item, Qt::Alignment alignment ) +{ + if ( item == nullptr ) + return; + + auto& engine = m_data->engine; + + if ( item->parentItem() == this ) + { + const int oldIndex = indexOf( item ); + if ( oldIndex >= 0 ) + { + // the item has been inserted before + + const bool doAppend = index < 0 || index >= engine.count(); + + if ( ( index == oldIndex ) || + ( doAppend && oldIndex == engine.count() - 1 ) ) + { + // already at its position, nothing to do + return; + } + + removeAt( oldIndex ); + } + } + + reparentItem( item ); + + const int numItems = engine.count(); + if ( index < 0 || index > numItems ) + index = numItems; + + engine.insertItem( item, index ); + engine.setAlignmentAt( index, alignment ); + + // Re-ordering the child items to have a a proper focus tab chain + + bool reordered = false; + + if ( index < engine.count() - 1 ) + { + for ( int i = index; i < engine.count(); i++ ) + { + if ( auto nextItem = engine.itemAt( i ) ) + { + item->stackBefore( nextItem ); + reordered = true; + + break; + } + } + } + + if ( !reordered ) + { + const auto children = childItems(); + if ( item != children.last() ) + item->stackAfter( children.last() ); + } + + + qskSetItemActive( this, item, true ); + +#if 1 + // Is there a way to block consecutive calls ??? + resetImplicitSize(); + polish(); +#endif } void QskLinearBox::addSpacer( qreal spacing, int stretchFactor ) @@ -178,21 +496,22 @@ void QskLinearBox::addSpacer( qreal spacing, int stretchFactor ) void QskLinearBox::insertSpacer( int index, qreal spacing, int stretchFactor ) { - spacing = qMax( spacing, 0.0 ); - stretchFactor = qMax( stretchFactor, 0 ); + auto& engine = m_data->engine; - QskLayoutItem* layoutItem; - if ( m_data->orientation == Qt::Horizontal ) - layoutItem = new QskLayoutItem( QSizeF( spacing, -1.0 ), stretchFactor, 0, 0 ); - else - layoutItem = new QskLayoutItem( QSizeF( -1.0, spacing ), stretchFactor, 0, 0 ); + const int numItems = engine.count(); + if ( index < 0 || index > numItems ) + index = numItems; + + engine.insertSpacerAt( index, spacing ); + + stretchFactor = qMax( stretchFactor, 0 ); + engine.setStretchFactorAt( index, stretchFactor ); #if 1 - if ( stretchFactor >= 0 ) - layoutItem->setStretchFactor( stretchFactor, m_data->orientation ); // already above ??? + // Is there a way to block consecutive calls ??? + resetImplicitSize(); + polish(); #endif - - insertLayoutItem( layoutItem, index ); } void QskLinearBox::addStretch( int stretchFactor ) @@ -205,270 +524,82 @@ void QskLinearBox::insertStretch( int index, int stretchFactor ) insertSpacer( index, 0, stretchFactor ); } +void QskLinearBox::setAlignment( int index, Qt::Alignment alignment ) +{ + if ( alignment != m_data->engine.alignmentAt( index ) ) + { + m_data->engine.setAlignmentAt( index, alignment ); + polish(); + } +} + +Qt::Alignment QskLinearBox::alignment( int index ) const +{ + return m_data->engine.alignmentAt( index ); +} + +void QskLinearBox::setAlignment( const QQuickItem* item, Qt::Alignment alignment ) +{ + setAlignment( indexOf( item ), alignment ); +} + +Qt::Alignment QskLinearBox::alignment( const QQuickItem* item ) const +{ + return alignment( indexOf( item ) ); +} + void QskLinearBox::setStretchFactor( int index, int stretchFactor ) { - if ( QskLayoutItem* layoutItem = engine().layoutItemAt( index ) ) + auto& engine = m_data->engine; + + if ( engine.stretchFactorAt( index ) != stretchFactor ) { - if ( layoutItem->stretchFactor( m_data->orientation ) != stretchFactor ) - { - layoutItem->setStretchFactor( stretchFactor, m_data->orientation ); - // activate(); - } + engine.setStretchFactorAt( index, stretchFactor ); + polish(); } } int QskLinearBox::stretchFactor( int index ) const { - if ( QskLayoutItem* layoutItem = engine().layoutItemAt( index ) ) - return layoutItem->stretchFactor( m_data->orientation ); - - return 0; + return m_data->engine.stretchFactorAt( index ); } void QskLinearBox::setStretchFactor( const QQuickItem* item, int stretch ) { - setStretchFactor( engine().indexOf( item ), stretch ); + setStretchFactor( indexOf( item ), stretch ); } int QskLinearBox::stretchFactor( const QQuickItem* item ) const { - return stretchFactor( engine().indexOf( item ) ); + return stretchFactor( indexOf( item ) ); } void QskLinearBox::setRetainSizeWhenHidden( int index, bool on ) { - auto layoutItem = engine().layoutItemAt( index ); - if ( layoutItem && on != layoutItem->retainSizeWhenHidden() ) + auto& engine = m_data->engine; + + if ( engine.retainSizeWhenHiddenAt( index ) != on ) { - layoutItem->setRetainSizeWhenHidden( on ); - invalidate(); + engine.setRetainSizeWhenHiddenAt( index, on ); + + resetImplicitSize(); + polish(); } } bool QskLinearBox::retainSizeWhenHidden( int index ) const { - if ( const auto layoutItem = engine().layoutItemAt( index ) ) - return layoutItem->retainSizeWhenHidden(); - - return false; + return m_data->engine.retainSizeWhenHiddenAt( index ); } void QskLinearBox::setRetainSizeWhenHidden( const QQuickItem* item, bool on ) { - setRetainSizeWhenHidden( engine().indexOf( item ), on ); + setRetainSizeWhenHidden( indexOf( item ), on ); } bool QskLinearBox::retainSizeWhenHidden( const QQuickItem* item ) const { - return retainSizeWhenHidden( engine().indexOf( item ) ); -} - -void QskLinearBox::setRowSpacing( int row, qreal spacing ) -{ - if ( row >= 0 ) - { - engine().setRowSpacing( row, spacing, Qt::Horizontal ); - activate(); - } -} - -qreal QskLinearBox::rowSpacing( int row ) const -{ - return engine().rowSpacing( row, Qt::Horizontal ); -} - -void QskLinearBox::setColumnSpacing( int column, qreal spacing ) -{ - if ( column >= 0 ) - { - engine().setRowSpacing( column, spacing, Qt::Vertical ); - activate(); - } -} - -qreal QskLinearBox::columnSpacing( int column ) const -{ - return engine().rowSpacing( column, Qt::Vertical ); -} - -void QskLinearBox::setRowStretchFactor( int row, int stretchFactor ) -{ - if ( row >= 0 ) - { - engine().setRowStretchFactor( row, stretchFactor, Qt::Vertical ); - activate(); - } -} - -int QskLinearBox::rowStretchFactor( int row ) const -{ - return engine().rowStretchFactor( row, Qt::Vertical ); -} - -void QskLinearBox::setColumnStretchFactor( int column, int stretchFactor ) -{ - if ( column >= 0 ) - { - engine().setRowStretchFactor( column, stretchFactor, Qt::Horizontal ); - activate(); - } -} - -int QskLinearBox::columnStretchFactor( int column ) const -{ - return engine().rowStretchFactor( column, Qt::Horizontal ); -} - -void QskLinearBox::setupLayoutItem( QskLayoutItem* layoutItem, int index ) -{ - int col = index % m_data->dimension; - int row = index / m_data->dimension; - - if ( m_data->orientation == Qt::Vertical ) - qSwap( col, row ); - - layoutItem->setFirstRow( col, Qt::Horizontal ); - layoutItem->setFirstRow( row, Qt::Vertical ); -} - -void QskLinearBox::layoutItemInserted( QskLayoutItem*, int index ) -{ - if ( index < itemCount() - 1 ) - rearrange(); -} - -void QskLinearBox::layoutItemRemoved( QskLayoutItem*, int index ) -{ - Q_UNUSED( index ) - rearrange(); -} - -void QskLinearBox::rearrange() -{ - bool doInvalidate = false; - - const int numItems = itemCount(); - - for ( int i = 0; i < numItems; i++ ) - { - int row = i / m_data->dimension; - int col = i % m_data->dimension; - - if ( m_data->orientation == Qt::Vertical ) - qSwap( col, row ); - - auto layoutItem = engine().layoutItemAt( i ); - - if ( layoutItem->firstColumn() != col || layoutItem->firstRow() != row ) - { - engine().removeItem( layoutItem ); - - layoutItem->setFirstRow( col, Qt::Horizontal ); - layoutItem->setFirstRow( row, Qt::Vertical ); - - engine().insertLayoutItem( layoutItem, i ); - - doInvalidate = true; - } - } - - if ( doInvalidate ) - invalidate(); -} - -QRectF QskLinearBox::alignedLayoutRect( const QRectF& rect ) const -{ - if ( m_data->extraSpacingAt == 0 ) - return rect; - - const QskLayoutEngine& engine = this->engine(); - - QRectF r = rect; - - // not 100% sure if this works for dynamic constraints - // and having extraSpacingAt for both directions ... - - if ( ( m_data->extraSpacingAt & Qt::LeftEdge ) || - ( m_data->extraSpacingAt & Qt::RightEdge ) ) - { - bool isExpandable = false; - - for ( int i = 0; i < engine.itemCount(); i++ ) - { - const QskLayoutItem* item = engine.layoutItemAt( i ); - - if ( !item->isIgnored() && - ( item->sizePolicy( Qt::Horizontal ) & QskSizePolicy::GrowFlag ) ) - { - isExpandable = true; - break; - } - } - - if ( !isExpandable ) - { - const qreal w = engine.widthForHeight( r.height() ); - - if ( m_data->extraSpacingAt & Qt::LeftEdge ) - { - if ( m_data->extraSpacingAt & Qt::RightEdge ) - { - r.moveLeft( r.center().x() - w / 2 ); - r.setWidth( w ); - } - else - { - r.setLeft( r.right() - w ); - } - } - else - { - r.setRight( r.left() + w ); - } - } - } - - if ( ( m_data->extraSpacingAt & Qt::TopEdge ) || - ( m_data->extraSpacingAt & Qt::BottomEdge ) ) - { - bool isExpandable = false; - - for ( int i = 0; i < engine.itemCount(); i++ ) - { - const QskLayoutItem* item = engine.layoutItemAt( i ); - - if ( !item->isIgnored() && - ( item->sizePolicy( Qt::Vertical ) & QskSizePolicy::GrowFlag ) ) - { - isExpandable = true; - break; - } - } - - if ( !isExpandable ) - { - const qreal h = engine.heightForWidth( r.width() ); - - if ( m_data->extraSpacingAt & Qt::TopEdge ) - { - if ( m_data->extraSpacingAt & Qt::BottomEdge ) - { - r.moveTop( r.center().y() - h / 2 ); - r.setHeight( h ); - } - else - { - r.setTop( r.bottom() - h ); - } - } - else - { - r.setBottom( r.top() + h ); - } - } - } - - return r; + return retainSizeWhenHidden( indexOf( item ) ); } #include "moc_QskLinearBox.cpp" diff --git a/src/layouts/QskLinearBox.h b/src/layouts/QskLinearBox.h index 2a3e2723..2deb0fd6 100644 --- a/src/layouts/QskLinearBox.h +++ b/src/layouts/QskLinearBox.h @@ -19,12 +19,17 @@ class QSK_EXPORT QskLinearBox : public QskIndexedLayoutBox WRITE setDimension NOTIFY dimensionChanged FINAL ) Q_PROPERTY( qreal spacing READ spacing - WRITE setSpacing RESET resetSpacing - NOTIFY spacingChanged FINAL ) + WRITE setSpacing RESET resetSpacing NOTIFY spacingChanged FINAL ) + + Q_PROPERTY( Qt::Alignment defaultAlignment READ defaultAlignment + WRITE setDefaultAlignment NOTIFY defaultAlignmentChanged ) Q_PROPERTY( Qt::Edges extraSpacingAt READ extraSpacingAt WRITE setExtraSpacingAt NOTIFY extraSpacingAtChanged ) + Q_PROPERTY( int entryCount READ entryCount() ) + Q_PROPERTY( bool empty READ isEmpty() ) + using Inherited = QskIndexedLayoutBox; public: @@ -32,9 +37,26 @@ class QSK_EXPORT QskLinearBox : public QskIndexedLayoutBox explicit QskLinearBox( Qt::Orientation, QQuickItem* parent = nullptr ); QskLinearBox( Qt::Orientation, uint dimension, QQuickItem* parent = nullptr ); - ~QskLinearBox() override; + bool isEmpty() const; + int entryCount() const; // items and spacers + +#ifdef QSK_LAYOUT_COMPAT + int itemCount() const { return entryCount(); } // items and spacers +#endif + + QQuickItem* itemAtIndex( int index ) const; + int indexOf( const QQuickItem* ) const; + + void removeItem( const QQuickItem* ); + void removeAt( int index ); + + QSizeF contentsSizeHint() const override; + + qreal heightForWidth( qreal width ) const override; + qreal widthForHeight( qreal height ) const override; + Qt::Orientation orientation() const; void setOrientation( Qt::Orientation ); @@ -44,10 +66,19 @@ class QSK_EXPORT QskLinearBox : public QskIndexedLayoutBox void setExtraSpacingAt( Qt::Edges ); Qt::Edges extraSpacingAt() const; + void setDefaultAlignment( Qt::Alignment ); + Qt::Alignment defaultAlignment() const; + void setSpacing( qreal spacing ); void resetSpacing(); qreal spacing() const; + Q_INVOKABLE void addItem( + QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); + + Q_INVOKABLE void 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 ); @@ -60,47 +91,51 @@ class QSK_EXPORT QskLinearBox : public QskIndexedLayoutBox void setStretchFactor( const QQuickItem*, int stretchFactor ); int stretchFactor( const QQuickItem* ) const; + void setAlignment( int index, Qt::Alignment ); + Qt::Alignment alignment( int index ) const; + + void setAlignment( const QQuickItem*, Qt::Alignment ); + Qt::Alignment alignment( const QQuickItem* ) const; + Q_INVOKABLE bool retainSizeWhenHidden( int index ) const; Q_INVOKABLE void setRetainSizeWhenHidden( int index, bool on ); bool retainSizeWhenHidden( const QQuickItem* ) const; void setRetainSizeWhenHidden( const QQuickItem*, bool on ); -#if 1 - 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; - - Q_INVOKABLE void setRowStretchFactor( int row, int stretchFactor ); - Q_INVOKABLE int rowStretchFactor( int row ) const; - - Q_INVOKABLE void setColumnStretchFactor( int column, int stretchFactor ); - Q_INVOKABLE int columnStretchFactor( int column ) const; -#endif - public Q_SLOTS: void transpose(); + void activate(); + void invalidate(); + void clear( bool autoDelete = false ); Q_SIGNALS: void orientationChanged(); void dimensionChanged(); + void defaultAlignmentChanged(); void spacingChanged(); void extraSpacingAtChanged(); protected: - QRectF alignedLayoutRect( const QRectF& ) const override; + bool event( QEvent* ) override; + void geometryChangeEvent( QskGeometryChangeEvent* ) override; + + void itemChange( ItemChange, const ItemChangeData& ) override; + void updateLayout() override; + + void autoAddItem( QQuickItem* ) override final; + void autoRemoveItem( QQuickItem* ) override final; private: - void setupLayoutItem( QskLayoutItem*, int index ) override; - void layoutItemInserted( QskLayoutItem*, int index ) override; - void layoutItemRemoved( QskLayoutItem*, int index ) override; - - void rearrange(); + void removeItemInternal( int index, bool autoDelete ); class PrivateData; std::unique_ptr< PrivateData > m_data; }; +inline bool QskLinearBox::isEmpty() const +{ + return entryCount() <= 0; +} + #endif diff --git a/src/layouts/QskLinearLayoutEngine.cpp b/src/layouts/QskLinearLayoutEngine.cpp new file mode 100644 index 00000000..5c0f7ee0 --- /dev/null +++ b/src/layouts/QskLinearLayoutEngine.cpp @@ -0,0 +1,1422 @@ +#include "QskLinearLayoutEngine.h" + +#include "QskLayoutHint.h" +#include "QskLayoutConstraint.h" +#include "QskSizePolicy.h" +#include "QskQuick.h" + +#include +#include +#include + +#ifdef QSK_LAYOUT_COMPAT +#include +#endif + +static constexpr qreal qskDefaultSpacing() +{ + // should be a skin hint + return 5.0; +} + +namespace +{ + class Range + { + public: + inline qreal end() const { return start + length; } + + qreal start = 0.0; + qreal length = 0.0; + }; + + class CellData + { + public: + QskLayoutHint hint; + int stretch = 0; + bool growFlag = false; // using stretch: -1 + }; + + class CellTable + { + public: + void invalidate(); + + void reset( int count, qreal constraint ); + void addCellData( int index, const CellData& ); + void finish(); + + bool setSpacing( qreal spacing ); + qreal spacing() const { return m_spacing; } + + void setExtraSpacingAt( Qt::Edges edges ) { m_extraSpacingAt = edges; } + + QVector< Range > cellRanges( 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: + QVector< Range > distributed( int which, qreal offset, qreal extra ) const; + QVector< Range > minimumExpanded( qreal size ) const; + QVector< Range > preferredStretched( qreal size ) const; + + QskLayoutHint m_boundingHint; + qreal m_constraint = -2; + + qreal m_spacing = 0; + Qt::Edges m_extraSpacingAt; + + int m_sumStretches = 0; + std::vector< CellData > m_cells; + }; + + class CellGeometries + { + public: + void invalidate() + { + boundingSize = QSizeF(); + rows.clear(); + columns.clear(); + } + + QRectF geometryAt( int row, int col ) const + { + return QRectF( + columns[ col ].start, rows[ row ].start, + columns[ col ].length, rows[ row ].length ); + } + + QSizeF boundingSize; + + QVector< Range > rows; + QVector< Range > columns; + }; +} + +void CellTable::invalidate() +{ + m_cells.clear(); + m_constraint = -2; +} + +void CellTable::reset( int count, qreal constraint ) +{ + m_cells.assign( count, CellData() ); + m_constraint = constraint; +} + +void CellTable::addCellData( int index, const CellData& data ) +{ + auto& combinedData = m_cells[ index ]; + + combinedData.growFlag |= data.growFlag; + combinedData.stretch = qMax( combinedData.stretch, data.stretch ); + + m_sumStretches += data.stretch; + + combinedData.hint.intersect( data.hint ); +} + +void CellTable::finish() +{ + qreal minimum = 0.0; + qreal preferred = 0.0; + qreal maximum = 0.0; + + if ( !m_cells.empty() ) + { + for ( auto& cellData : m_cells ) + { + minimum += cellData.hint.minimum(); + preferred += cellData.hint.preferred(); + + if ( cellData.stretch == 0 && !cellData.growFlag ) + maximum += cellData.hint.preferred(); + else + maximum += cellData.hint.maximum(); // overflow ??? + } + + const qreal spacing = ( m_cells.size() - 1 ) * m_spacing; + + minimum += spacing; + preferred += spacing; + maximum += spacing; + } + + m_boundingHint.setMinimum( minimum ); + m_boundingHint.setPreferred( preferred ); + m_boundingHint.setMaximum( maximum ); +} + +bool CellTable::setSpacing( qreal spacing ) +{ + if ( m_spacing != spacing ) + { + m_spacing = spacing; + return true; + } + + return false; +} + +QVector< Range > CellTable::cellRanges( qreal size ) const +{ + QVector< Range > ranges; + + if ( size <= m_boundingHint.minimum() ) + { + ranges = distributed( Qt::MinimumSize, 0.0, 0.0 ); + } + else if ( size < m_boundingHint.preferred() ) + { + ranges = minimumExpanded( size ); + } + else if ( size <= m_boundingHint.maximum() ) + { + ranges = preferredStretched( size ); + } + else + { + const qreal padding = size - m_boundingHint.maximum(); + + qreal offset = 0.0; + qreal extra = 0.0;; + + if ( m_extraSpacingAt == Qt::LeftEdge ) + { + 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_cells.size(); + } + + ranges = distributed( Qt::MaximumSize, offset, extra ); + } + + return ranges; +} + +QVector< Range > CellTable::distributed( + int which, qreal offset, const qreal extra ) const +{ + qreal fillSpacing = 0.0; + + QVector< Range > ranges( m_cells.size() ); + + for ( int i = 0; i < ranges.count(); i++ ) + { + auto& range = ranges[i]; + + offset += fillSpacing; + fillSpacing = m_spacing; + + range.start = offset; + range.length = m_cells[i].hint.size( which ) + extra; + + offset += range.length; + } + + return ranges; +} + +QVector< Range > CellTable::minimumExpanded( qreal size ) const +{ + QVector< Range > ranges( m_cells.size() ); + + qreal fillSpacing = 0.0; + qreal offset = 0.0; + + /* + We have different options how to distribute the availabe space + + - according to the preferred sizes + + - items with a larger preferred size are stretchier: this is + what QSK_LAYOUT_COMPAT does ( compatible with QGridLayoutEngine ) + + - somehow using the stretch factors + */ + +#if QSK_LAYOUT_COMPAT + + /* + Code does not make much sense, but this is what QGridLayoutEngine does. + The implementation is intended to help during the migration, but is supposed + to be removed then TODO ... + */ + qreal sumFactors = 0.0; + QVarLengthArray< qreal > factors( m_cells.size() ); + + 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++ ) + { + const auto& hint = m_cells[i].hint; + + const qreal l = hint.preferred() - hint.minimum(); + + factors[i] = l * std::pow( available / desired, l / desired ); + sumFactors += factors[i]; + } + + + for ( uint i = 0; i < m_cells.size(); i++ ) + { + const auto& hint = m_cells[i].hint; + + auto& range = ranges[i]; + + offset += fillSpacing; + fillSpacing = m_spacing; + + range.start = offset; + range.length = hint.minimum() + available * ( factors[i] / sumFactors ); + + offset += range.length; + } +#else + const qreal factor = ( size - m_boundingHint.minimum() ) / + ( m_boundingHint.preferred() - m_boundingHint.minimum() ); + + for ( uint i = 0; i < m_cells.size(); i++ ) + { + const auto& hint = m_cells[i].hint; + + auto& range = ranges[i]; + + offset += fillSpacing; + fillSpacing = m_spacing; + + range.start = offset; + range.length = hint.minimum() + factor * ( hint.preferred() - hint.minimum() ); + + offset += range.length; + } +#endif + + return ranges; +} + +QVector< Range > CellTable::preferredStretched( qreal size ) const +{ + const int count = m_cells.size(); + auto sumSizes = size - ( count - 1 ) * m_spacing; + + qreal sumFactors = 0.0; + QVarLengthArray< qreal > factors( count ); + + for ( int i = 0; i < count; i++ ) + { + const auto& hint = m_cells[i].hint; + + if ( hint.preferred() >= hint.maximum() ) + { + factors[i] = 0.0; + } + else + { + if ( m_sumStretches == 0 ) + factors[i] = m_cells[i].growFlag ? 1.0 : 0.0; + else + factors[i] = m_cells[i].stretch; + } + + sumFactors += factors[i]; + } + + QVector< Range > ranges( count ); + + Q_FOREVER + { + bool done = true; + + for ( int i = 0; i < count; i++ ) + { + if ( factors[i] < 0.0 ) + continue; + + const auto size = sumSizes * factors[i] / sumFactors; + + const auto& hint = m_cells[i].hint; + const auto boundedSize = + qBound( hint.preferred(), size, hint.maximum() ); + + if ( boundedSize != size ) + { + ranges[i].length = boundedSize; + sumSizes -= boundedSize; + sumFactors -= factors[i]; + factors[i] = -1.0; + + done = false; + } + } + + if ( done ) + break; + } + + qreal offset = 0; + qreal fillSpacing = 0.0; + + for ( int i = 0; i < count; i++ ) + { + auto& range = ranges[i]; + const auto& factor = factors[i]; + + offset += fillSpacing; + fillSpacing = m_spacing; + + range.start = offset; + + if ( factor >= 0.0 ) + { + if ( factor > 0.0 ) + range.length = sumSizes * factor / sumFactors; + else + range.length = m_cells[i].hint.preferred(); + } + + offset += range.length; + } + + return ranges; +} + +namespace +{ + class EntryData + { + public: + EntryData( QQuickItem* item ); + EntryData( qreal spacing ); + + EntryData& operator=( const EntryData& ); + + bool isIgnored() const; + bool isConstrained( Qt::Orientation ) 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; } + + private: + + union + { + QQuickItem* m_item; + qreal m_spacer; + }; + + int m_stretch; + + unsigned int m_alignment : 8; + 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 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 updateCellTable( Qt::Orientation, + const QVector< Range >& constraints, CellTable& ) const; + + QskLayoutConstraint::Type constraintType() const; + + void invalidate(); + + private: + CellData cellData( 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_constrainedType : 3; + + unsigned int m_defaultAlignment : 8; + unsigned int m_orientation : 2; + }; + +} + +EntryData::EntryData( QQuickItem* item ) + : m_item( item ) + , m_stretch( -1 ) + , m_alignment( 0 ) + , m_isSpacer( false ) + , m_retainSizeWhenHidden( false ) +{ +} + +EntryData::EntryData( qreal spacing ) + : m_spacer( spacing ) + , m_stretch( -1 ) + , m_alignment( 0 ) + , m_isSpacer( true ) + , m_retainSizeWhenHidden( false ) +{ +} + +EntryData& EntryData::operator=( const EntryData& other ) +{ + m_isSpacer = other.m_isSpacer; + + if ( other.m_isSpacer ) + m_spacer = other.m_spacer; + else + m_item = other.m_item; + + m_stretch = other.m_stretch; + m_alignment = other.m_alignment; + m_retainSizeWhenHidden = other.m_retainSizeWhenHidden; + + return *this; +} + +bool EntryData::isIgnored() const +{ + if ( !m_isSpacer && !m_retainSizeWhenHidden ) + return !qskIsVisibleToParent( m_item ); + + return false; +} + +bool EntryData::isConstrained( Qt::Orientation orientation ) const +{ + if ( m_isSpacer ) + return false; + + switch( QskLayoutConstraint::constraintType( m_item ) ) + { + case QskLayoutConstraint::WidthForHeight: + return orientation == Qt::Horizontal; + + case QskLayoutConstraint::HeightForWidth: + return orientation == Qt::Vertical; + + default: + return false; + } +} + +EntryTable::EntryTable( Qt::Orientation orientation, uint dimension ) + : m_dimension( dimension ) + , m_sumIgnored( -1 ) + , m_constrainedType( -1 ) + , m_defaultAlignment( Qt::AlignLeft | Qt::AlignVCenter ) + , m_orientation( orientation ) +{ +} + +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 ) +{ + if ( index >= 0 && index < count() ) + { + m_entries.erase( m_entries.begin() + index ); + 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_constrainedType = -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_constrainedType < 0 ) + { + m_constrainedType = QskLayoutConstraint::Unconstrained; + + for ( const auto& entry : m_entries ) + { + const auto itemType = QskLayoutConstraint::constraintType( entry.item() ); + + if ( itemType != QskLayoutConstraint::Unconstrained ) + { + if ( m_constrainedType == QskLayoutConstraint::Unconstrained ) + { + m_constrainedType = itemType; + } + else if ( m_constrainedType != itemType ) + { + qWarning( "QskLinearLayoutEngine: conflicting constraints"); + m_constrainedType = QskLayoutConstraint::Unconstrained; + } + } + } + } + + return static_cast< QskLayoutConstraint::Type >( m_constrainedType ); +} + +CellData EntryTable::cellData( const EntryData& entry, + Qt::Orientation orientation, qreal constraint ) const +{ + int stretch = 0; + bool growFlag = true; + qreal minimum, preferred, maximum; + + if ( const auto item = entry.item() ) + { + const auto policy = QskLayoutConstraint::sizePolicy( item ).policy( orientation ); + + if ( constraint >= 0.0 ) + { + if ( !entry.isConstrained( orientation ) ) + constraint = -1.0; + } + + const auto expandFlags = QskSizePolicy::GrowFlag | QskSizePolicy::ExpandFlag; + + if ( ( policy & QskSizePolicy::ShrinkFlag ) && + ( policy & expandFlags ) && ( policy & QskSizePolicy::IgnoreFlag ) ) + { + // we don't need to calculate the preferred size + + minimum = QskLayoutConstraint::sizeHint( + item, Qt::MinimumSize, orientation, constraint ); + + maximum = QskLayoutConstraint::sizeHint( + item, Qt::MaximumSize, orientation, constraint ); + + preferred = minimum; + } + else + { + preferred = QskLayoutConstraint::sizeHint( + item, Qt::PreferredSize, orientation, constraint ); + + minimum = maximum = preferred; + + if ( policy & QskSizePolicy::ShrinkFlag ) + { + minimum = QskLayoutConstraint::sizeHint( + item, Qt::MinimumSize, orientation, constraint ); + } + + if ( policy & expandFlags ) + { + maximum = QskLayoutConstraint::sizeHint( + item, Qt::MaximumSize, orientation, constraint ); + } + + if ( policy & QskSizePolicy::IgnoreFlag ) + preferred = minimum; + } + + if ( orientation == m_orientation ) + { + if ( entry.stretch() < 0 ) + stretch = ( policy & QskSizePolicy::ExpandFlag ) ? 1 : 0; + else + stretch = entry.stretch(); + } + + growFlag = policy & QskSizePolicy::GrowFlag; + } + else + { + // a spacer + + if ( orientation == m_orientation ) + { + minimum = preferred = maximum = entry.spacer(); + + // >= 0 ??? + if ( entry.stretch() > 0 ) + maximum = QskLayoutConstraint::unlimited; + + stretch = qMax( entry.stretch(), 0 ); + } + else + { + minimum = 0.0; + preferred = 0.0; + maximum = QskLayoutConstraint::unlimited; + } + } + + CellData cellData; + cellData.hint = QskLayoutHint( minimum, preferred, maximum ); + cellData.stretch = stretch; + cellData.growFlag = growFlag; + + return cellData; +} + +void EntryTable::updateCellTable( Qt::Orientation orientation, + const QVector< Range >& constraints, CellTable& cellTable ) const +{ + const auto count = effectiveCount( orientation ); + const qreal constraint = + constraints.isEmpty() ? -1.0 : constraints.last().end(); + + if ( ( cellTable.constraint() == constraint ) + && ( cellTable.count() == count ) ) + { + return; // already up to date + } + + cellTable.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 data = cellData( entry, orientation, cellConstraint ); + cellTable.addCellData( index2, data ); + + if ( m_orientation != orientation ) + { + if ( ++index1 == m_dimension ) + { + index1 = 0; + index2++; + } + } + else + { + if ( ++index2 == m_dimension ) + { + index2 = 0; + index1++; + } + } + } + + cellTable.finish(); +} + +// --------- + +static inline void qskUpdateCellTable( Qt::Orientation orientation, + const QVector< Range >& constraints, + const EntryTable& entryTable, CellTable& cellTable ) +{ + entryTable.updateCellTable( orientation, constraints, cellTable ); +} + +static inline void qskUpdateCellTable( Qt::Orientation orientation, + const EntryTable& entryTable, CellTable& cellTable ) +{ + entryTable.updateCellTable( orientation, QVector< Range >(), cellTable ); +} + +class QskLinearLayoutEngine::PrivateData +{ + public: + + PrivateData( Qt::Orientation orientation, uint dimension ) + : entryTable( orientation, dimension ) + , blockInvalidate( false ) + { + rowTable.setSpacing( qskDefaultSpacing() ); + colTable.setSpacing( qskDefaultSpacing() ); + } + + EntryTable entryTable; + + Qt::LayoutDirection visualDirection = Qt::LeftToRight; + Qt::Edges extraSpacingAt; + + CellTable colTable; + CellTable rowTable; + + CellGeometries geometries; + + /* + 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; +}; + +QskLinearLayoutEngine::QskLinearLayoutEngine( Qt::Orientation orientation, uint dimension ) + : m_data( new PrivateData( orientation, dimension ) ) +{ +} + +QskLinearLayoutEngine::~QskLinearLayoutEngine() +{ +} + +void QskLinearLayoutEngine::setOrientation( Qt::Orientation orientation ) +{ + if ( m_data->entryTable.setOrientation( orientation ) ) + invalidate( CellCache | LayoutCache ); +} + +Qt::Orientation QskLinearLayoutEngine::orientation() const +{ + return m_data->entryTable.orientation(); +} + +void QskLinearLayoutEngine::setDimension( uint dimension ) +{ + if ( dimension < 1 ) + dimension = 1; + + if ( m_data->entryTable.setDimension( dimension ) ) + invalidate( CellCache | LayoutCache ); +} + +uint QskLinearLayoutEngine::dimension() const +{ + return m_data->entryTable.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 ); +} + +void QskLinearLayoutEngine::setRetainSizeWhenHiddenAt( int index, bool on ) +{ + if ( m_data->entryTable.setRetainSizeWhenHiddenAt( index, on ) ) + invalidate( CellCache | LayoutCache ); +} + +bool QskLinearLayoutEngine::retainSizeWhenHiddenAt( int index ) const +{ + return m_data->entryTable.retainSizeWhenHiddenAt( index ); +} + +void QskLinearLayoutEngine::setStretchFactorAt( int index, int stretchFactor ) +{ + if ( m_data->entryTable.setStretchFactorAt( index, stretchFactor ) ) + invalidate( CellCache | LayoutCache ); +} + +int QskLinearLayoutEngine::stretchFactorAt( int index ) const +{ + return m_data->entryTable.stretchFactorAt( index ); +} + +void QskLinearLayoutEngine::setAlignmentAt( int index, Qt::Alignment alignment ) +{ + m_data->entryTable.setAlignmentAt( index, alignment ); +} + +Qt::Alignment QskLinearLayoutEngine::alignmentAt( int index ) const +{ + return m_data->entryTable.alignmentAt( index ); +} + +void QskLinearLayoutEngine::setSpacing( qreal spacing, Qt::Orientations orientations ) +{ + if ( spacing < 0.0 ) + spacing = 0.0; + + bool doInvalidate = false; + + if ( orientations & Qt::Horizontal ) + doInvalidate |= m_data->colTable.setSpacing( spacing ); + + if ( orientations & Qt::Vertical ) + doInvalidate |= m_data->rowTable.setSpacing( spacing ); + + if ( doInvalidate ) + invalidate( CellCache | LayoutCache ); +} + +qreal QskLinearLayoutEngine::spacing( Qt::Orientation orientation ) const +{ + if ( orientation == Qt::Horizontal ) + return m_data->colTable.spacing(); + else + return m_data->rowTable.spacing(); +} + +void QskLinearLayoutEngine::setExtraSpacingAt( Qt::Edges edges ) +{ + if ( edges == m_data->extraSpacingAt ) + return; + + m_data->extraSpacingAt = edges; + + Qt::Edges colEdges = edges & ~( Qt::TopEdge | Qt::BottomEdge ); + m_data->colTable.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->rowTable.setExtraSpacingAt( rowEdges ); + + invalidate( LayoutCache ); +} + +Qt::Edges QskLinearLayoutEngine::extraSpacingAt() const +{ + return m_data->extraSpacingAt; +} + +void QskLinearLayoutEngine::setDefaultAlignment( Qt::Alignment alignment ) +{ + m_data->entryTable.setDefaultAlignment( alignment ); +} + +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 ); +} + +QQuickItem* QskLinearLayoutEngine::itemAt( int index ) const +{ + return m_data->entryTable.itemAt( index ); +} + +int QskLinearLayoutEngine::spacerAt( int index ) const +{ + return m_data->entryTable.spacerAt( index ); +} + +void QskLinearLayoutEngine::invalidate( int what ) +{ + if ( m_data->blockInvalidate ) + return; + + if ( what & EntryCache ) + m_data->entryTable.invalidate(); + + if ( what & CellCache ) + { + m_data->rowTable.invalidate(); + m_data->colTable.invalidate(); + } + + if ( what & LayoutCache ) + m_data->geometries.invalidate(); +} + +void QskLinearLayoutEngine::setGeometries( const QRectF& rect ) +{ + if ( m_data->entryTable.count() == 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++ ) + { + if ( entryTable.isIgnoredAt( i ) ) + continue; + + if ( auto item = entryTable.itemAt( i ) ) + { + 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 ); + } + + if ( entryTable.orientation() == Qt::Horizontal ) + { + if ( ++col == entryTable.dimension() ) + { + col = 0; + row++; + } + } + else + { + if ( ++row == entryTable.dimension() ) + { + row = 0; + col++; + } + } + } +} + +QSizeF QskLinearLayoutEngine::sizeHint( Qt::SizeHint which, const QSizeF& constraint ) const +{ + const auto& entryTable = m_data->entryTable; + + if ( entryTable.effectiveCount() == 0 ) + return QSizeF( 0.0, 0.0 ); + + const auto constraintType = m_data->entryTable.constraintType(); + + auto& colTable = m_data->colTable; + auto& rowTable = m_data->rowTable; + + m_data->blockInvalidate = true; + + if ( ( constraint.width() >= 0 ) && + ( constraintType == QskLayoutConstraint::HeightForWidth ) ) + { + qskUpdateCellTable( Qt::Horizontal, entryTable, colTable ); + + const auto cellConstraints = colTable.cellRanges( constraint.width() ); + qskUpdateCellTable( Qt::Vertical, cellConstraints, entryTable, rowTable ); + } + else if ( ( constraint.height() >= 0 ) && + ( constraintType == QskLayoutConstraint::WidthForHeight ) ) + { + qskUpdateCellTable( Qt::Vertical, entryTable, rowTable ); + + const auto cellConstraints = rowTable.cellRanges( constraint.height() ); + qskUpdateCellTable( Qt::Horizontal, cellConstraints, entryTable, colTable ); + } + else + { + qskUpdateCellTable( Qt::Horizontal, entryTable, colTable ); + qskUpdateCellTable( Qt::Vertical, entryTable, rowTable ); + } + + m_data->blockInvalidate = false; + + const qreal width = colTable.boundingHint().size( which ); + const qreal height = rowTable.boundingHint().size( which ); + + return QSizeF( width, height ); +} + +qreal QskLinearLayoutEngine::widthForHeight( qreal height ) 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(); +} + +void QskLinearLayoutEngine::setVisualDirection(Qt::LayoutDirection direction) +{ + m_data->visualDirection = direction; +} + +Qt::LayoutDirection QskLinearLayoutEngine::visualDirection() const +{ + return m_data->visualDirection; +} + +void QskLinearLayoutEngine::updateCellGeometries( const QSizeF& size ) +{ + auto& geometries = m_data->geometries; + geometries.boundingSize = size; + + auto& colTable = m_data->colTable; + auto& rowTable = m_data->rowTable; + auto& entryTable = m_data->entryTable; + + const QVector< Range > noConstraints; + + switch( entryTable.constraintType() ) + { + case QskLayoutConstraint::WidthForHeight: + { + qskUpdateCellTable( Qt::Vertical, entryTable, rowTable ); + geometries.rows = rowTable.cellRanges( size.height() ); + + qskUpdateCellTable( Qt::Horizontal, geometries.rows, entryTable, colTable ); + geometries.columns = colTable.cellRanges( size.width() ); + + break; + } + case QskLayoutConstraint::HeightForWidth: + { + qskUpdateCellTable( Qt::Horizontal, entryTable, colTable ); + geometries.columns = colTable.cellRanges( size.width() ); + + qskUpdateCellTable( Qt::Vertical, geometries.columns, entryTable, rowTable ); + geometries.rows = rowTable.cellRanges( size.height() ); + + break; + } + default: + { + qskUpdateCellTable( Qt::Horizontal, entryTable, colTable ); + geometries.columns = colTable.cellRanges( size.width() ); + + qskUpdateCellTable( Qt::Vertical, entryTable, rowTable ); + geometries.rows = rowTable.cellRanges( size.height() ); + } + } +} + +qreal QskLinearLayoutEngine::defaultSpacing( Qt::Orientation ) const +{ + return qskDefaultSpacing(); +} diff --git a/src/layouts/QskLinearLayoutEngine.h b/src/layouts/QskLinearLayoutEngine.h new file mode 100644 index 00000000..bf4262d3 --- /dev/null +++ b/src/layouts/QskLinearLayoutEngine.h @@ -0,0 +1,91 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_LINEAR_LAYOUT_ENGINE_H +#define QSK_LINEAR_LAYOUT_ENGINE_H + +#include +#include +#include + +class QQuickItem; + +class QskLinearLayoutEngine +{ + public: + QskLinearLayoutEngine( Qt::Orientation, uint dimension ); + ~QskLinearLayoutEngine(); + + Qt::Orientation orientation() const; + void setOrientation( Qt::Orientation ); + + void setDimension( uint dimension ); + uint dimension() const; + + void setDefaultAlignment( Qt::Alignment ); + Qt::Alignment defaultAlignment() const; + + void setExtraSpacingAt( Qt::Edges ); + Qt::Edges extraSpacingAt() const; + + int count() const; + + int rowCount() const; + int columnCount() const; + + void setSpacing( qreal spacing, Qt::Orientations ); + qreal spacing( Qt::Orientation ) const; + qreal defaultSpacing( Qt::Orientation ) const; + + void insertItem( QQuickItem*, int index ); + void addItem( QQuickItem* ); + + void insertSpacerAt( int index, qreal spacing ); + void addSpacer( qreal spacing ); + + void removeAt( int index ); + + QQuickItem* itemAt( int index ) const; + int spacerAt( int index ) const; + + void setRetainSizeWhenHiddenAt( int index, bool on ); + bool retainSizeWhenHiddenAt( int index ) const; + + void setAlignmentAt( int index, Qt::Alignment ); + Qt::Alignment alignmentAt( int index ) const; + + void 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; + + void setVisualDirection( Qt::LayoutDirection ); + Qt::LayoutDirection visualDirection() 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) + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/layouts/QskStackBox.cpp b/src/layouts/QskStackBox.cpp index c31353ab..5eb1d6c4 100644 --- a/src/layouts/QskStackBox.cpp +++ b/src/layouts/QskStackBox.cpp @@ -4,12 +4,12 @@ *****************************************************************************/ #include "QskStackBox.h" -#include "QskLayoutConstraint.h" -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" #include "QskStackBoxAnimator.h" +#include "QskLayoutConstraint.h" +#include "QskEvent.h" +#include "QskQuick.h" -#include +#include static qreal qskConstrainedValue( QskLayoutConstraint::Type type, const QskControl* control, qreal widthOrHeight ) @@ -35,16 +35,35 @@ static qreal qskConstrainedValue( QskLayoutConstraint::Type type, return constrainedValue; } +namespace +{ + class ItemInfo + { + public: + inline ItemInfo() + : item( nullptr ) + { + } + + inline ItemInfo( Qt::Alignment alignment, QQuickItem* item ) + : alignment( alignment ) + , item( item ) + { + } + + Qt::Alignment alignment; + QQuickItem* item; + }; +} + class QskStackBox::PrivateData { public: - PrivateData() - : currentIndex( -1 ) - { - } - - int currentIndex; + QVector< ItemInfo > itemInfos; QPointer< QskStackBoxAnimator > animator; + + int currentIndex = -1; + Qt::Alignment defaultAlignment = Qt::AlignLeft | Qt::AlignVCenter; }; QskStackBox::QskStackBox( QQuickItem* parent ) @@ -53,7 +72,7 @@ QskStackBox::QskStackBox( QQuickItem* parent ) } QskStackBox::QskStackBox( bool autoAddChildren, QQuickItem* parent ) - : Inherited( parent ) + : QskIndexedLayoutBox( parent ) , m_data( new PrivateData() ) { setAutoAddChildren( autoAddChildren ); @@ -63,6 +82,22 @@ QskStackBox::~QskStackBox() { } +void QskStackBox::setDefaultAlignment( Qt::Alignment alignment ) +{ + if ( alignment != m_data->defaultAlignment ) + { + m_data->defaultAlignment = alignment; + Q_EMIT defaultAlignmentChanged( alignment ); + + polish(); + } +} + +Qt::Alignment QskStackBox::defaultAlignment() const +{ + return m_data->defaultAlignment; +} + void QskStackBox::setAnimator( QskStackBoxAnimator* animator ) { if ( m_data->animator == animator ) @@ -103,6 +138,33 @@ QskStackBoxAnimator* QskStackBox::effectiveAnimator() return nullptr; } +int QskStackBox::itemCount() const +{ + return m_data->itemInfos.count(); +} + +QQuickItem* QskStackBox::itemAtIndex( int index ) const +{ + if ( index >= 0 && index < m_data->itemInfos.count() ) + return m_data->itemInfos[ index ].item; + + return nullptr; +} + +int QskStackBox::indexOf( const QQuickItem* item ) const +{ + if ( item && ( item->parentItem() != this ) ) + { + for ( int i = 0; i < m_data->itemInfos.count(); i++ ) + { + if ( item == m_data->itemInfos[i].item ) + return i; + } + } + + return -1; +} + QQuickItem* QskStackBox::currentItem() const { return itemAtIndex( m_data->currentIndex ); @@ -113,44 +175,6 @@ int QskStackBox::currentIndex() const return m_data->currentIndex; } -void QskStackBox::layoutItemRemoved( QskLayoutItem*, int index ) -{ - if ( index == m_data->currentIndex ) - { - int newIndex = m_data->currentIndex; - if ( newIndex == itemCount() ) - newIndex--; - - m_data->currentIndex = -1; - - if ( newIndex >= 0 ) - setCurrentIndex( index ); - } - else if ( index < m_data->currentIndex ) - { - m_data->currentIndex--; - // currentIndexChanged ??? - } - - auto& engine = this->engine(); - if ( engine.itemCount() > 0 && engine.itemAt( 0, 0 ) == nullptr ) - { - /* - Using QGridLayoutEngine for a stack layout is actually - not a good ideas. Until we have a new implementation, - we need to work around situations, where the layout does - not work properly with having several items in the - same cell. - In this particular situation we need to fix, that we lost - the item from engine.q_grid[0]. - Calling transpose has this side effect. - - */ - engine.transpose(); - engine.transpose(); // reverting the call before - } -} - void QskStackBox::setCurrentIndex( int index ) { if ( index < 0 || index >= itemCount() ) @@ -169,11 +193,6 @@ void QskStackBox::setCurrentIndex( int index ) if ( window() && isVisible() && isInitiallyPainted() && animator ) { - // When being hidden, the geometry is not updated. - // So we do it now. - - adjustItemAt( index ); - // start the animation animator->setStartIndex( m_data->currentIndex ); animator->setEndIndex( index ); @@ -201,26 +220,229 @@ void QskStackBox::setCurrentItem( const QQuickItem* item ) setCurrentIndex( indexOf( item ) ); } -QSizeF QskStackBox::layoutItemsSizeHint() const +void QskStackBox::addItem( QQuickItem* item, Qt::Alignment alignment ) { - qreal width = -1; - qreal height = -1; + insertItem( -1, item, alignment ); +} - QSizeF constraint( -1, -1 ); - Qt::Orientations constraintOrientation = 0; +void QskStackBox::insertItem( + int index, QQuickItem* item, Qt::Alignment alignment ) +{ + if ( item == nullptr ) + return; - const auto& engine = this->engine(); - for ( int i = 0; i < engine.itemCount(); i++ ) + reparentItem( item ); + + if ( qskIsTransparentForPositioner( item ) ) { - const auto layoutItem = engine.layoutItemAt( i ); + // giving a warning, or ignoring the insert ??? + qskSetTransparentForPositioner( item, false ); + } - if ( layoutItem->hasDynamicConstraint() ) + const bool doAppend = ( index < 0 ) || ( index >= itemCount() ); + + if ( item->parentItem() == this ) + { + const int oldIndex = indexOf( item ); + if ( oldIndex >= 0 ) { - constraintOrientation |= layoutItem->dynamicConstraintOrientation(); + // the item had been inserted before + + if ( ( index == oldIndex ) || ( doAppend && ( oldIndex == itemCount() - 1 ) ) ) + { + // already in place + + auto& itemInfo = m_data->itemInfos[oldIndex]; + + if ( alignment != itemInfo.alignment ) + { + itemInfo.alignment = alignment; + polish(); + } + + return; + } + + m_data->itemInfos.removeAt( oldIndex ); + } + } + + if ( doAppend ) + index = itemCount(); + + m_data->itemInfos.insert( index, { alignment, item } ); + + const int oldCurrentIndex = m_data->currentIndex; + + if ( m_data->itemInfos.count() == 1 ) + { + m_data->currentIndex = 0; + item->setVisible( true ); + } + else + { + item->setVisible( false ); + + if ( index <= m_data->currentIndex ) + m_data->currentIndex++; + } + + if ( oldCurrentIndex != m_data->currentIndex ) + Q_EMIT currentIndexChanged( m_data->currentIndex ); + + resetImplicitSize(); + polish(); +} + +void QskStackBox::removeAt( int index ) +{ + removeItemInternal( index, false ); +} + +void QskStackBox::removeItemInternal( int index, bool unparent ) +{ + if ( index < 0 || index >= m_data->itemInfos.count() ) + return; + + if ( !unparent ) + { + if ( auto item = m_data->itemInfos[ index ].item ) + { + if ( item->parentItem() == this ) + item->setParentItem( nullptr ); + } + } + + m_data->itemInfos.removeAt( index ); + + if ( index <= m_data->currentIndex ) + Q_EMIT currentIndexChanged( --m_data->currentIndex ); + + resetImplicitSize(); + polish(); +} + +void QskStackBox::removeItem( const QQuickItem* item ) +{ + removeAt( indexOf( item ) ); +} + +void QskStackBox::autoAddItem( QQuickItem* item ) +{ + removeAt( indexOf( item ) ); +} + +void QskStackBox::autoRemoveItem( QQuickItem* item ) +{ + removeItemInternal( indexOf( item ), false ); +} + +void QskStackBox::clear( bool autoDelete ) +{ + for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) ) + { + auto item = itemInfo.item; + + if( autoDelete && ( item->parent() == this ) ) + { + delete item; } else { - const QSizeF hint = layoutItem->sizeHint( Qt::PreferredSize, constraint ); + item->setParentItem( nullptr ); + } + } + + m_data->itemInfos.clear(); + + if ( m_data->currentIndex >= 0 ) + { + m_data->currentIndex = -1; + Q_EMIT currentIndexChanged( m_data->currentIndex ); + } +} + +void QskStackBox::setAlignment( const QQuickItem* item, Qt::Alignment alignment ) +{ + setAlignmentAt( indexOf( item ), alignment ); +} + +Qt::Alignment QskStackBox::alignment( const QQuickItem* item ) const +{ + return alignmentAt( indexOf( item ) ); +} + +void QskStackBox::setAlignmentAt( int index, Qt::Alignment alignment ) +{ + if ( index < 0 || index >= m_data->itemInfos.count() ) + return; + + m_data->itemInfos[ index ].alignment = alignment; + + if ( index == m_data->currentIndex ) + polish(); +} + +Qt::Alignment QskStackBox::alignmentAt( int index ) const +{ + if ( index >= 0 && index < m_data->itemInfos.count() ) + return m_data->itemInfos[ index ].alignment; + + return Qt::Alignment(); +} + +QRectF QskStackBox::geometryForItemAt( int index ) const +{ + const auto r = layoutRect(); + + if ( index >= 0 && index < m_data->itemInfos.count() ) + { + const auto& info = m_data->itemInfos[ index ]; + + const auto align = info.alignment ? info.alignment : m_data->defaultAlignment; + return QskLayoutConstraint::itemRect( info.item, r, align ); + } + + return QRectF( r.x(), r.y(), 0.0, 0.0 ); +} + +void QskStackBox::updateLayout() +{ + const auto idx = m_data->currentIndex; + + if ( idx >= 0 ) + { + const auto rect = geometryForItemAt( idx ); + qskSetItemGeometry( m_data->itemInfos[ idx ].item, rect ); + } +} + +QSizeF QskStackBox::contentsSizeHint() const +{ +#if 1 + if ( itemCount() == 0 ) + return QSizeF( 0, 0 ); +#endif + + qreal width = -1; + qreal height = -1; + + using namespace QskLayoutConstraint; + + int constraintTypes = Unconstrained; + + for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) ) + { + const auto item = itemInfo.item; + + const auto type = constraintType( item ); + if ( type != Unconstrained ) + { + constraintTypes |= type; + } + else + { + const QSizeF hint = effectiveConstraint( item, Qt::PreferredSize ); if ( hint.width() >= width ) width = hint.width(); @@ -232,40 +454,39 @@ QSizeF QskStackBox::layoutItemsSizeHint() const #if 1 // does this work ??? - if ( constraintOrientation & Qt::Horizontal ) + + if ( constraintTypes & WidthForHeight ) { - constraint.setWidth( -1 ); - constraint.setHeight( height ); + const QSizeF constraint( -1, height ); - for ( int i = 0; i < engine.itemCount(); i++ ) + for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) ) { - const auto layoutItem = engine.layoutItemAt( i ); + const auto item = itemInfo.item; - if ( layoutItem->hasDynamicConstraint() && - layoutItem->dynamicConstraintOrientation() == Qt::Horizontal ) + if ( constraintType( item ) == WidthForHeight ) { - const QSizeF hint = layoutItem->sizeHint( Qt::PreferredSize, constraint ); - if ( hint.width() > width ) - width = hint.width(); + const QSizeF hint = QskLayoutConstraint::sizeHint( + item, Qt::PreferredSize, constraint ); + + width = qMax( width, hint.width() ); } } } - if ( constraintOrientation & Qt::Vertical ) + if ( constraintTypes & HeightForWidth ) { - constraint.setWidth( width ); - constraint.setHeight( -1 ); + const QSizeF constraint( width, -1 ); - for ( int i = 0; i < engine.itemCount(); i++ ) + for ( const auto& itemInfo : qskAsConst( m_data->itemInfos ) ) { - const auto layoutItem = engine.layoutItemAt( i ); + const auto item = itemInfo.item; - if ( layoutItem->hasDynamicConstraint() && - layoutItem->dynamicConstraintOrientation() == Qt::Vertical ) + if ( constraintType( item ) == HeightForWidth ) { - const QSizeF hint = layoutItem->sizeHint( Qt::PreferredSize, constraint ); - if ( hint.height() > height ) - height = hint.height(); + const QSizeF hint = QskLayoutConstraint::sizeHint( + item, Qt::PreferredSize, constraint ); + + height = qMax( height, hint.height() ); } } } @@ -286,39 +507,25 @@ qreal QskStackBox::widthForHeight( qreal height ) const QskLayoutConstraint::WidthForHeight, this, height, qskConstrainedValue ); } -void QskStackBox::layoutItemInserted( QskLayoutItem* layoutItem, int index ) +bool QskStackBox::event( QEvent* event ) { - Q_UNUSED( index ) - - QQuickItem* item = layoutItem->item(); - if ( item == nullptr ) - return; - -#if 1 - /* - In general QGridLayoutEngine supports having multiple entries - in one cell, but well ... - So we have to go away from using it and doing the simple use case of - a stack layout manually. TODO ... - - One problem we ran into is, that a cell is considered being hidden, - when the first entry is ignored. So for the moment we simply set the - retainSizeWhenHidden flag, with the cost of having geometry updates - for invisible updates. - */ - layoutItem->setRetainSizeWhenHidden( true ); -#endif - if ( itemCount() == 1 ) + switch ( static_cast< int >( event->type() ) ) { - m_data->currentIndex = 0; - item->setVisible( true ); + case QEvent::LayoutRequest: + { + resetImplicitSize(); + polish(); + break; + } + case QEvent::ContentsRectChange: + case QskEvent::GeometryChange: + { + polish(); + break; + } + } - Q_EMIT currentIndexChanged( m_data->currentIndex ); - } - else - { - item->setVisible( false ); - } + return Inherited::event( event ); } #include "moc_QskStackBox.cpp" diff --git a/src/layouts/QskStackBox.h b/src/layouts/QskStackBox.h index fd7d2d25..62e4b9ee 100644 --- a/src/layouts/QskStackBox.h +++ b/src/layouts/QskStackBox.h @@ -20,7 +20,7 @@ class QSK_EXPORT QskStackBox : public QskIndexedLayoutBox Q_PROPERTY( QQuickItem* currentItem READ currentItem WRITE setCurrentItem NOTIFY currentItemChanged ) - using Inherited = QskIndexedLayoutBox; + using Inherited = QskBox; public: explicit QskStackBox( QQuickItem* parent = nullptr ); @@ -28,36 +28,74 @@ class QSK_EXPORT QskStackBox : public QskIndexedLayoutBox ~QskStackBox() override; + bool isEmpty() const; + + int itemCount() const; + QQuickItem* itemAtIndex( int index ) const; + int indexOf( const QQuickItem* ) const; + + void addItem( + QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); + + void insertItem( + int index, QQuickItem*, Qt::Alignment alignment = Qt::Alignment() ); + + void removeItem( const QQuickItem* ); + void removeAt( int index ); + QQuickItem* currentItem() const; int currentIndex() const; - qreal heightForWidth( qreal width ) const override; - qreal widthForHeight( qreal height ) const override; + void setDefaultAlignment( Qt::Alignment ); + Qt::Alignment defaultAlignment() const; + + void setAlignmentAt( int index, Qt::Alignment ); + Qt::Alignment alignmentAt( int index ) const; + + void setAlignment( const QQuickItem*, Qt::Alignment ); + Qt::Alignment alignment( const QQuickItem* ) const; void setAnimator( QskStackBoxAnimator* ); const QskStackBoxAnimator* animator() const; QskStackBoxAnimator* animator(); + QSizeF contentsSizeHint() const override; + qreal heightForWidth( qreal width ) const override; + qreal widthForHeight( qreal height ) const override; + + QRectF geometryForItemAt( int index ) const; + + Q_SIGNALS: + void defaultAlignmentChanged( Qt::Alignment ); + public Q_SLOTS: void setCurrentIndex( int index ); void setCurrentItem( const QQuickItem* ); + void clear( bool autoDelete = false ); Q_SIGNALS: void currentIndexChanged( int index ); void currentItemChanged( QQuickItem* ); protected: + bool event( QEvent* ) override; + void updateLayout() override; + + void autoAddItem( QQuickItem* ) override final; + void autoRemoveItem( QQuickItem* ) override final; + QskStackBoxAnimator* effectiveAnimator(); - QSizeF layoutItemsSizeHint() const override; private: - friend class QskStackBoxAnimator; - - void layoutItemInserted( QskLayoutItem*, int index ) override; - void layoutItemRemoved( QskLayoutItem*, int index ) override; + void removeItemInternal( int index, bool autoDelete ); class PrivateData; std::unique_ptr< PrivateData > m_data; }; +inline bool QskStackBox::isEmpty() const +{ + return itemCount() <= 0; +} + #endif diff --git a/src/layouts/QskStackBoxAnimator.cpp b/src/layouts/QskStackBoxAnimator.cpp index bfc16109..235eada5 100644 --- a/src/layouts/QskStackBoxAnimator.cpp +++ b/src/layouts/QskStackBoxAnimator.cpp @@ -4,9 +4,9 @@ *****************************************************************************/ #include "QskStackBoxAnimator.h" -#include "QskLayoutEngine.h" -#include "QskLayoutItem.h" #include "QskStackBox.h" +#include "QskEvent.h" +#include "QskQuick.h" static Qsk::Direction qskDirection( Qt::Orientation orientation, int from, int to, int itemCount ) @@ -91,17 +91,19 @@ QskStackBox* QskStackBoxAnimator::stackBox() const return static_cast< QskStackBox* >( parent() ); } -QskLayoutItem* QskStackBoxAnimator::layoutItemAt( int index ) const +QQuickItem* QskStackBoxAnimator::itemAt( int index ) const { - return stackBox()->engine().layoutItemAt( + return stackBox()->itemAtIndex( ( index == 0 ) ? m_startIndex : m_endIndex ); } QskStackBoxAnimator1::QskStackBoxAnimator1( QskStackBox* parent ) : QskStackBoxAnimator( parent ) , m_orientation( Qt::Horizontal ) + , m_isDirty( false ) , m_hasClip( false ) { + // catching geometryChanges to know about resizing } QskStackBoxAnimator1::~QskStackBoxAnimator1() @@ -131,97 +133,72 @@ void QskStackBoxAnimator1::setup() m_direction = qskDirection( m_orientation, startIndex(), endIndex(), stackBox->itemCount() ); - for ( int i = 0; i < 2; i++ ) - { - QskLayoutItem* layoutItem = layoutItemAt( i ); - if ( layoutItem ) - { - QQuickItem* item = layoutItem->item(); - const Qt::Orientation orientation = this->orientation(); - - m_itemOffset[ i ] = - ( orientation == Qt::Horizontal ) ? item->x() : item->y(); - - if ( i == 1 ) - { - // now move the new item outside of - // the visible area and then "show" it - - if ( orientation == Qt::Horizontal ) - item->setX( stackBox->width() ); - else - item->setY( stackBox->height() ); - - item->setVisible( true ); - } - - // we don't want the engine() to interfere, when - // controlling the item by the animation - - layoutItem->setUpdateMode( QskLayoutItem::UpdateNone ); - } - } - m_hasClip = stackBox->clip(); if ( !m_hasClip ) stackBox->setClip( true ); + + stackBox->installEventFilter( this ); + m_isDirty = true; } void QskStackBoxAnimator1::advance( qreal value ) { auto stackBox = this->stackBox(); + const bool isHorizontal = m_orientation == Qt::Horizontal; for ( int i = 0; i < 2; i++ ) { - QskLayoutItem* layoutItem = layoutItemAt( i ); - if ( layoutItem == nullptr ) - continue; - - if ( layoutItem->isGeometryDirty() ) + if ( auto item = itemAt( i ) ) { - // the layout tried to replace the item, but we - // want to have control over the position. But we - // also lost resizing - that's why we have to do it here - // manually + QRectF rect = qskItemGeometry( item ); - stackBox->adjustItemAt( ( i == 0 ) ? startIndex() : endIndex() ); + if ( m_isDirty ) + { + const int index = ( i == 0 ) ? startIndex() : endIndex(); + rect = stackBox->geometryForItemAt( index ); - QQuickItem* item = layoutItem->item(); - m_itemOffset[ i ] = - ( m_orientation == Qt::Horizontal ) ? item->x() : item->y(); - } + m_itemOffset[ i ] = isHorizontal ? rect.x() : rect.y(); + } - QQuickItem* item = layoutItem->item(); + qreal x, y; - if ( m_orientation == Qt::Horizontal ) - { - const qreal off = stackBox->width() * ( value - i ); + if ( isHorizontal ) + { + qreal off = stackBox->width() * ( value - i ); + if ( m_direction == Qsk::LeftToRight ) + off = -off; - if ( m_direction == Qsk::LeftToRight ) - item->setX( m_itemOffset[ i ] - off ); + x = m_itemOffset[ i ] + off; + y = rect.y(); + } else - item->setX( m_itemOffset[ i ] + off ); - } - else - { - const qreal off = stackBox->height() * ( value - i ); + { + qreal off = stackBox->height() * ( value - i ); + if ( m_direction == Qsk::BottomToTop ) + off = -off; - if ( m_direction == Qsk::TopToBottom ) - item->setY( m_itemOffset[ i ] + off ); - else - item->setY( m_itemOffset[ i ] - off ); + x = rect.x(); + y = m_itemOffset[ i ] + off; + } + + qskSetItemGeometry( item, x, y, rect.width(), rect.height() ); + + if ( !item->isVisible() ) + item->setVisible( true ); } } + + m_isDirty = false; } void QskStackBoxAnimator1::done() { for ( int i = 0; i < 2; i++ ) { - if ( QskLayoutItem* layoutItem = layoutItemAt( i ) ) + if ( auto item = itemAt( i ) ) { - layoutItem->setUpdateMode( QskLayoutItem::UpdateWhenVisible ); - layoutItem->item()->setVisible( i == 1 ); + item->removeEventFilter( this ); + item->setVisible( i == 1 ); } } @@ -229,6 +206,25 @@ void QskStackBoxAnimator1::done() stackBox()->setClip( false ); } +bool QskStackBoxAnimator1::eventFilter( QObject* object, QEvent* event ) +{ + if ( !m_isDirty && object == stackBox() ) + { + switch( static_cast< int >( event->type() ) ) + { + case QskEvent::GeometryChange: + case QskEvent::ContentsRectChange: + case QskEvent::LayoutRequest: + { + m_isDirty = true; + break; + } + } + } + + return QObject::eventFilter( object, event ); +} + QskStackBoxAnimator3::QskStackBoxAnimator3( QskStackBox* parent ) : QskStackBoxAnimator( parent ) { @@ -240,33 +236,30 @@ QskStackBoxAnimator3::~QskStackBoxAnimator3() void QskStackBoxAnimator3::setup() { - QskLayoutItem* layoutItem = layoutItemAt( 1 ); - if ( layoutItem ) + if ( auto item = itemAt( 1 ) ) { - layoutItem->item()->setOpacity( 0.0 ); - layoutItem->item()->setVisible( true ); + item->setOpacity( 0.0 ); + item->setVisible( true ); } } void QskStackBoxAnimator3::advance( qreal value ) { - QskLayoutItem* layoutItem1 = layoutItemAt( 0 ); - if ( layoutItem1 ) - layoutItem1->item()->setOpacity( 1.0 - value ); + if ( auto item1 = itemAt( 0 ) ) + item1->setOpacity( 1.0 - value ); - QskLayoutItem* layoutItem2 = layoutItemAt( 1 ); - if ( layoutItem2 ) - layoutItem2->item()->setOpacity( value ); + if ( auto item2 = itemAt( 1 ) ) + item2->setOpacity( value ); } void QskStackBoxAnimator3::done() { for ( int i = 0; i < 2; i++ ) { - if ( QskLayoutItem* layoutItem = layoutItemAt( i ) ) + if ( auto item = itemAt( i ) ) { - layoutItem->item()->setOpacity( 1.0 ); - layoutItem->item()->setVisible( i == 1 ); // not here !! + item->setOpacity( 1.0 ); + item->setVisible( i == 1 ); // not here !! } } } diff --git a/src/layouts/QskStackBoxAnimator.h b/src/layouts/QskStackBoxAnimator.h index e0ef54e5..286b3278 100644 --- a/src/layouts/QskStackBoxAnimator.h +++ b/src/layouts/QskStackBoxAnimator.h @@ -12,14 +12,14 @@ #include class QskStackBox; -class QskLayoutItem; +class QQuickItem; class QSK_EXPORT QskStackBoxAnimator : public QObject, public QskAnimator { Q_OBJECT public: - QskStackBoxAnimator( QskStackBox* parent ); + QskStackBoxAnimator( QskStackBox* ); ~QskStackBoxAnimator() override; void setStartIndex( int index ); @@ -30,7 +30,7 @@ class QSK_EXPORT QskStackBoxAnimator : public QObject, public QskAnimator protected: QskStackBox* stackBox() const; - QskLayoutItem* layoutItemAt( int index ) const; + QQuickItem* itemAt( int index ) const; private: int m_startIndex; @@ -42,13 +42,15 @@ class QSK_EXPORT QskStackBoxAnimator1 : public QskStackBoxAnimator Q_OBJECT public: - QskStackBoxAnimator1( QskStackBox* parent ); + QskStackBoxAnimator1( QskStackBox* ); ~QskStackBoxAnimator1() override; void setOrientation( Qt::Orientation ); Qt::Orientation orientation() const; protected: + bool eventFilter( QObject*, QEvent* ) override; + void setup() override; void advance( qreal value ) override; void done() override; @@ -58,6 +60,7 @@ class QSK_EXPORT QskStackBoxAnimator1 : public QskStackBoxAnimator Qt::Orientation m_orientation : 2; Qsk::Direction m_direction : 4; + bool m_isDirty : 1; bool m_hasClip : 1; }; @@ -66,7 +69,7 @@ class QSK_EXPORT QskStackBoxAnimator3 : public QskStackBoxAnimator Q_OBJECT public: - QskStackBoxAnimator3( QskStackBox* parent ); + QskStackBoxAnimator3( QskStackBox* ); ~QskStackBoxAnimator3() override; protected: diff --git a/src/src.pro b/src/src.pro index dc0cd5a5..5435cebe 100644 --- a/src/src.pro +++ b/src/src.pro @@ -233,25 +233,23 @@ SOURCES += \ HEADERS += \ layouts/QskGridBox.h \ + layouts/QskGridLayoutEngine.h \ layouts/QskIndexedLayoutBox.h \ - layouts/QskLayoutEngine.h \ - layouts/QskLayoutBox.h \ layouts/QskLayoutConstraint.h \ layouts/QskLayoutHint.h \ - layouts/QskLayoutItem.h \ layouts/QskLinearBox.h \ + layouts/QskLinearLayoutEngine.h \ layouts/QskStackBoxAnimator.h \ layouts/QskStackBox.h SOURCES += \ layouts/QskGridBox.cpp \ + layouts/QskGridLayoutEngine.cpp \ layouts/QskIndexedLayoutBox.cpp \ - layouts/QskLayoutBox.cpp \ layouts/QskLayoutConstraint.cpp \ layouts/QskLayoutHint.cpp \ - layouts/QskLayoutEngine.cpp \ - layouts/QskLayoutItem.cpp \ layouts/QskLinearBox.cpp \ + layouts/QskLinearLayoutEngine.cpp \ layouts/QskStackBoxAnimator.cpp \ layouts/QskStackBox.cpp