aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Arve Sæther <jan-arve.saether@qt.io>2026-03-29 12:42:54 +0200
committerJan Arve Sæther <jan-arve.saether@qt.io>2026-03-30 10:14:49 +0200
commit3a714efe0672ae1ba06f09864f64957560c69b91 (patch)
tree490e85145b57254624cbcbfd9ca6d1c846a2a474
parent98ef405b1bfe552899d21e5af4d4738871f6c535 (diff)
Fix size hint calculationv6.10.36.10.3
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.cpp83
-rw-r--r--tests/auto/quick/qquicklayouts/data/tst_flexboxlayout.qml104
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)
+ }
}
}