diff options
| author | Jan Arve Sæther <jan-arve.saether@qt.io> | 2026-03-29 12:42:54 +0200 |
|---|---|---|
| committer | Jan Arve Sæther <jan-arve.saether@qt.io> | 2026-03-30 10:14:49 +0200 |
| commit | 3a714efe0672ae1ba06f09864f64957560c69b91 (patch) | |
| tree | 490e85145b57254624cbcbfd9ca6d1c846a2a474 | |
| parent | 98ef405b1bfe552899d21e5af4d4738871f6c535 (diff) | |
This fixes some bugs when calculating size hints, notably:
* Include gaps
* minimum size hints was not calculated correctly (was always (0,0))
* maximum size hints was always infinite
size hints are now calculated differently depending on wrap or not (for
brevity, the following only describes it for direction == Row):
If it doesn't wrap:
minimumWidth:
the sum of all childrens minimumWidths (+gap+margins)
minimumHeight:
the maximum of all the childrens minimumHeights
preferredWidth:
the sum of all childrens preferredWidths (+gap+margins)
preferredHeight:
the maximum of all the childrens preferredHeights
maximumWidth:
the sum of all childrens maximumWidths (+gap+margins)
maximumHeight:
the maximum of all the childrens maximumHeights
Otherwise, if it wraps:
minimumWidth:
the maximum of all the childrens minimumWidths
minimumHeight:
the maximum of all the childrens minimumHeights
preferredWidth: (this might change in future)
the sum of all childrens preferredWidths (+gap+margins)
preferredHeight:
the maximum of all the childrens preferredHeights
maximumWidth:
the sum of all childrens maximumWidths (+gap+margins)
maximumHeight:
the sum of all childrens maximumHeights (+gap+margins)
Except from preferred size, the above is consistent with how e.g.
QLabel's size hints are
Task-number: QTBUG-141055
Pick-to: dev 6.10 6.11
Change-Id: Idcdda62e9bafaf6cdc65438867a276b845c07f20
Reviewed-by: SanthoshKumar Selvaraj <santhosh.kumar.selvaraj@qt.io>
| -rw-r--r-- | src/quicklayouts/qquickflexboxlayoutengine.cpp | 83 | ||||
| -rw-r--r-- | tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml | 104 |
2 files changed, 172 insertions, 15 deletions
diff --git a/src/quicklayouts/qquickflexboxlayoutengine.cpp b/src/quicklayouts/qquickflexboxlayoutengine.cpp index 882109f4db..d6d2451459 100644 --- a/src/quicklayouts/qquickflexboxlayoutengine.cpp +++ b/src/quicklayouts/qquickflexboxlayoutengine.cpp @@ -160,7 +160,17 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const minS = QSizeF(0,0); prefS = QSizeF(0,0); - maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity()); + maxS = QSizeF(0,0); + + qreal flexColumnGap = 0; + qreal flexRowGap = 0; + bool wrap = false; + auto *qFlexLayout = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem()); + if (qFlexLayout) { + flexColumnGap = qFlexLayout->columnGap(); + flexRowGap = qFlexLayout->rowGap(); + wrap = qFlexLayout->wrap() != QQuickFlexboxLayout::NoWrap; + } const int count = itemCount(); for (int i = 0; i < count; ++i) { @@ -172,15 +182,15 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // meant the flex child item has a const width or height but // want to stretch vertically or horizontally if (flexLayoutItem->isItemStreched()) { - if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) { + if (qFlexLayout) { // Reset the size of the child item if the parent sets // its property 'align-item' to strecth // Note: The child item can also override the parent // align-item property through align-self // (this is FlexboxLayout.alignItem for quick items) flexLayoutItem->resetSize(); - if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row || - parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) { + if (qFlexLayout->direction() == QQuickFlexboxLayout::Row || + qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) { flexLayoutItem->setWidth(hints.pref().width()); } else { flexLayoutItem->setHeight(hints.pref().height()); @@ -215,10 +225,27 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // Parent pref, min, max width/height: // Sum of the pref, min and max width/height of // the child items - if (auto *qFlexLayout = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) { - if (qFlexLayout->wrap() == QQuickFlexboxLayout::NoWrap) { - if (qFlexLayout->direction() == QQuickFlexboxLayout::Row || - qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) { + if (qFlexLayout) { + const bool last = (i == count - 1); + if (qFlexLayout->direction() == QQuickFlexboxLayout::Row || + qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) { + if (wrap) { + // Minimum size + minS.setWidth(qMax(minS.width(), hints.min().width())); + minS.setHeight(qMax(minS.height(), hints.min().height())); + // Preferred size. Same as no-wrap preferred size + prefS.setWidth(prefS.width() + hints.pref().width()); + prefS.setHeight(qMax(prefS.height(), hints.pref().height())); + // Maximum size + maxS.setWidth(maxS.width() + hints.max().width()); + maxS.setHeight(maxS.height() + hints.max().height()); + + if (!last) { + prefS.rwidth() += flexColumnGap; + maxS.rwidth() += flexColumnGap; + maxS.rheight() += flexRowGap; + } + } else { // Minimum size minS.setWidth(minS.width() + hints.min().width()); minS.setHeight(qMax(minS.height(), hints.min().height())); @@ -228,8 +255,33 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // Maximum size maxS.setWidth(maxS.width() + hints.max().width()); maxS.setHeight(qMax(maxS.height(), hints.max().height())); - } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column || - qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) { + + if (!last) { + minS.rwidth() += flexColumnGap; + prefS.rwidth() += flexColumnGap; + maxS.rwidth() += flexColumnGap; + } + } + + } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column || + qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) { + if (wrap) { + // Minimum size + minS.setWidth(qMax(minS.width(), hints.min().width())); + minS.setHeight(qMax(minS.height(), hints.min().height())); + // Preferred size. Same as no-wrap preferred size + prefS.setWidth(qMax(prefS.width(), hints.pref().width())); + prefS.setHeight(prefS.height() + hints.pref().height()); + // Maximum size + maxS.setWidth(maxS.width() + hints.max().width()); + maxS.setHeight(maxS.height() + hints.max().height()); + + if (!last) { + prefS.rheight() += flexRowGap; + maxS.rwidth() += flexColumnGap; + maxS.rheight() += flexRowGap; + } + } else { // Minimum size minS.setWidth(qMax(minS.width(), hints.min().width())); minS.setHeight(minS.height() + hints.min().height()); @@ -239,12 +291,13 @@ QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const // Maximum size maxS.setWidth(qMax(maxS.width(), hints.max().width())); maxS.setHeight(maxS.height() + hints.max().height()); + + if (!last) { + minS.rheight() += flexRowGap; + prefS.rheight() += flexRowGap; + maxS.rheight() += flexRowGap; + } } - } else if (qFlexLayout->wrap() == QQuickFlexboxLayout::Wrap || - qFlexLayout->wrap() == QQuickFlexboxLayout::WrapReverse) { - minS += hints.min(); - prefS += hints.pref(); - maxS += hints.max(); } } } diff --git a/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml index 4e66cfb009..5334f7a977 100644 --- a/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml +++ b/tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml @@ -807,5 +807,109 @@ Item { compare(rectItem2.width, 20) compare(rectItem2.height, 100) } + + Component { + id: flexboxWithSizeHintsLayoutComponent + FlexboxLayout { + gap: 0 + Rectangle { + Layout.minimumWidth: 1 + Layout.minimumHeight: 1 + implicitWidth: 10 + implicitHeight: 10 + Layout.maximumWidth: 100 + Layout.maximumHeight: 100 + } + Rectangle { + Layout.minimumWidth: 2 + Layout.minimumHeight: 2 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 200 + Layout.maximumHeight: 200 + } + } + } + + function test_sizeHints() { + let flexboxLayout = createTemporaryObject(flexboxWithSizeHintsLayoutComponent, container) + waitForItemPolished(flexboxLayout) + // Test size hints of main axis. Since nothing has Layout.fillWidth: true, no items will flex + // It is therefore considered as a fixed-width layout + expectFailContinue("", "Layout.minimumWidth is 2." + + " FlexboxLayout should take fillWidth/sizePolicy into consideration when normalizing size hints") + compare(flexboxLayout.Layout.minimumWidth, 30) + compare(flexboxLayout.implicitWidth, 30) + expectFailContinue("", "Layout.maximumWidth is ∞." + + " FlexboxLayout should take fillWidth/sizePolicy into consideration when normalizing size hints") + compare(flexboxLayout.Layout.maximumWidth, 30) + + // Make first item flexible + let item0 = flexboxLayout.children[0] + item0.Layout.fillWidth = true + waitForItemPolished(flexboxLayout) + compare(flexboxLayout.Layout.minimumWidth, 3) + compare(flexboxLayout.implicitWidth, 30) + + // The semantic of the implicit value of Layout.maximumWidth is unclear. Currently it is infinite. + // But this is inconsistent with RowLayout: + // RowLayout will have its maximumWidth be the sum of all the childrens *effective* maximum widths (+spacings) + // (effective maximum width is just: (Layout.fillWidth ? Layout.maximumWidth : implicitWidth), but this is + // beside the point here) + + // If the FlexboxLayout have the RowLayout semantic, and it is a child of another layout, the parent layout + // will respect its maximumWidth (so FlexboxLayout.justifyContent will be pointless) + + // However, if the FlexboxLayout is anchored to a parent Item with anchors.fill, the FlexboxLayout can become + // wider than its maximumWidth. In that scenario, FlexboxLayout.justifyContent has a purpose + compare(flexboxLayout.Layout.maximumWidth, 300) + + // restore back to no flexing + item0.Layout.fillWidth = false + waitForItemPolished(flexboxLayout) + + // Test size hints of cross-axis + compare(flexboxLayout.Layout.minimumHeight, 2) + compare(flexboxLayout.implicitHeight, 20) + // actually semantic of this is unclear as described for Layout.maximumWidth above + compare(flexboxLayout.Layout.maximumHeight, 200) + + // ----- gap: 5 ----- + // Now add a gap, test if size hints are adjusted accordingly + flexboxLayout.gap = 5 + waitForItemPolished(flexboxLayout) + compare(flexboxLayout.Layout.minimumWidth, 8) + compare(flexboxLayout.implicitWidth, 35) + compare(flexboxLayout.Layout.maximumWidth, 305) + + // If the FlexboxLayout is not wrapping, gap shouldn't change the cross-axis size hint + // Test size hints of cross-axis + compare(flexboxLayout.Layout.minimumHeight, 2) + compare(flexboxLayout.implicitHeight, 20) + // actually semantic of this is unclear as described for Layout.maximumWidth above + compare(flexboxLayout.Layout.maximumHeight, 200) + + + // flex-wrap: wrap + /* + if (wrap) + minimumWidth: width of the largest minimumwidth + minimumHeight: height of the largest minimumHeight + + implicitWidth: width for being close to the golden ratio (+ gaps) + implicitWidth: height for being close to the golden ratio. (+ gaps) + + maximumWidth: sum of all maximumWidths (+ gaps) + maximumHeight: sum of all maximumHeights (+ gaps) + */ + flexboxLayout.wrap = FlexboxLayout.Wrap + waitForItemPolished(flexboxLayout) + compare(flexboxLayout.Layout.minimumWidth, 2) + compare(flexboxLayout.Layout.minimumHeight, 2) + compare(flexboxLayout.implicitWidth, 30 + 5) + compare(flexboxLayout.implicitHeight, 20) + compare(flexboxLayout.Layout.maximumWidth, 300 + 5) + compare(flexboxLayout.Layout.maximumHeight, 300 + 5) + } } } |
