1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtQuickLayouts/private/qquickflexboxlayoutengine_p.h>
5
6QT_BEGIN_NAMESPACE
7
8QQuickFlexboxLayoutEngine::QQuickFlexboxLayoutEngine()
9{
10}
11
12QQuickFlexboxLayoutEngine::~QQuickFlexboxLayoutEngine()
13{
14 clearItems();
15}
16
17void QQuickFlexboxLayoutEngine::setFlexboxParentItem(QQuickFlexboxLayoutItem *item)
18{
19 Q_ASSERT(item != nullptr);
20 if (qobject_cast<QQuickFlexboxLayout *>(object: item->quickItem())) {
21 m_flexboxParentItem = item;
22 // Yoga parent item shouldn't have measure function
23 if (m_flexboxParentItem->hasMeasureFunc())
24 m_flexboxParentItem->resetMeasureFunc();
25 }
26}
27
28void QQuickFlexboxLayoutEngine::clearItems()
29{
30 for (auto &flexItem: m_flexLayoutItems)
31 delete flexItem;
32 m_flexLayoutItems.clear();
33 // Clear the size hints as we removed all the items from the flex layout
34 for (int hintIndex = 0; hintIndex < Qt::NSizeHints; hintIndex++)
35 m_cachedSizeHints[hintIndex] = QSizeF();
36}
37
38void QQuickFlexboxLayoutEngine::insertItem(QQuickFlexboxLayoutItem *item)
39{
40 m_flexboxParentItem->insertChild(item, index: m_flexLayoutItems.count());
41 m_flexLayoutItems.append(t: item);
42}
43
44int QQuickFlexboxLayoutEngine::itemCount() const
45{
46 return m_flexLayoutItems.count();
47}
48
49QQuickItem *QQuickFlexboxLayoutEngine::itemAt(int index) const
50{
51 if (index < 0 || index >= m_flexLayoutItems.count())
52 return nullptr;
53 return m_flexLayoutItems.at(i: index)->quickItem();
54}
55
56QQuickFlexboxLayoutItem *QQuickFlexboxLayoutEngine::findFlexboxLayoutItem(QQuickItem *item) const
57{
58 if (!item || (m_flexLayoutItems.count() <= 0))
59 return nullptr;
60 auto iterator = std::find_if(first: m_flexLayoutItems.cbegin(), last: m_flexLayoutItems.cend(),
61 pred: [item] (QQuickFlexboxLayoutItem *flexLayoutItem){
62 return (flexLayoutItem->quickItem() == item);
63 });
64 return (iterator == m_flexLayoutItems.cend()) ? nullptr : *iterator;
65}
66
67void QQuickFlexboxLayoutEngine::collectItemSizeHints(QQuickFlexboxLayoutItem *flexItem, QSizeF *sizeHints) const
68{
69 QQuickLayoutAttached *info = nullptr;
70 QQuickLayout::effectiveSizeHints_helper(item: flexItem->quickItem(), cachedSizeHints: sizeHints, info: &info, useFallbackToWidthOrHeight: true);
71
72 if (!info)
73 return;
74
75 // Set layout margins to the flex item (Layout.margins)
76 if (info->isMarginsSet())
77 flexItem->setFlexMargin(edge: QQuickFlexboxLayout::EdgeAll, value: info->margins());
78 if (info->isLeftMarginSet())
79 flexItem->setFlexMargin(edge: QQuickFlexboxLayout::EdgeLeft, value: info->leftMargin());
80 if (info->isRightMarginSet())
81 flexItem->setFlexMargin(edge: QQuickFlexboxLayout::EdgeRight, value: info->rightMargin());
82 if (info->isTopMarginSet())
83 flexItem->setFlexMargin(edge: QQuickFlexboxLayout::EdgeTop, value: info->topMargin());
84 if (info->isBottomMarginSet())
85 flexItem->setFlexMargin(edge: QQuickFlexboxLayout::EdgeBottom, value: info->bottomMargin());
86
87 // Set child item to grow, shrink and stretch depending on the layout
88 // properties.
89 // If Layout.fillWidth or Layout.fillHeight is set as true, then the child
90 // item within the layout can grow or shrink (considering the minimum and
91 // maximum sizes) along the main axis which depends upon the flex
92 // direction.
93 // If both Layout.fillWidth and Layout.fillHeight are set as true, then the
94 // child item within the layout need to grow or shrink in cross section and
95 // it require stretch need to be set for the yoga flex child item.
96 if (info->isFillWidthSet() || info->isFillHeightSet()) {
97 // Set stretch to child item both width and height
98 if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(object: m_flexboxParentItem->quickItem())) {
99 if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
100 parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) {
101 // If the Layout.fillHeight not been set, the preferred height
102 // will be set as height
103 if (!info->fillHeight())
104 flexItem->setHeight(sizeHints[Qt::PreferredSize].height());
105 flexItem->setFlexBasis(value: sizeHints[Qt::PreferredSize].width(), reset: !info->fillWidth());
106 // Set child item to grow on main-axis (i.e. the flex
107 // direction)
108 flexItem->setItemGrowAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
109 // Set child item to shrink on main-axis (i.e. the flex
110 // direction)
111 flexItem->setItemShrinkAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
112 }
113 else {
114 // If the Layout.fillWidth not been set, the preferred width
115 // will be set as width
116 if (!info->fillWidth())
117 flexItem->setWidth(sizeHints[Qt::PreferredSize].width());
118 flexItem->setFlexBasis(value: sizeHints[Qt::PreferredSize].height(), reset: !info->fillHeight());
119 // Set child item to grow on main-axis (i.e. the flex
120 // direction)
121 flexItem->setItemGrowAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
122 // Set child item to shrink on main-axis (i.e. the flex
123 // direction)
124 flexItem->setItemShrinkAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
125 }
126 }
127 // If the Layout.fillHeight not been set, the preferred height will be
128 // set as height in the previous condition. Otherwise (for
129 // Layout.fillHeight been set as true), make flex item to AlignStretch.
130 // Thus it can also grow vertically.
131 // Note: The same applies for Layout.fillWidth to grow horizontally.
132 if ((qt_is_nan(d: flexItem->size().width()) && info->fillWidth()) ||
133 (qt_is_nan(d: flexItem->size().height()) && info->fillHeight())) {
134 flexItem->setItemStretchAlongCrossSection();
135 } else {
136 flexItem->inheritItemStretchAlongCrossSection();
137 }
138 }
139}
140
141SizeHints &QQuickFlexboxLayoutEngine::cachedItemSizeHints(int index) const
142{
143 QQuickFlexboxLayoutItem *flexBoxLayoutItem = m_flexLayoutItems.at(i: index);
144 Q_ASSERT(flexBoxLayoutItem);
145 SizeHints &hints = flexBoxLayoutItem->cachedItemSizeHints();
146 if (!hints.min().isValid())
147 collectItemSizeHints(flexItem: flexBoxLayoutItem, sizeHints: hints.array);
148 return hints;
149}
150
151QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const
152{
153 QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
154 if (!askingFor.isValid()) {
155 QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
156 QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
157 QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
158
159 minS = QSizeF(0,0);
160 prefS = QSizeF(0,0);
161 maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity());
162
163 const int count = itemCount();
164 for (int i = 0; i < count; ++i) {
165 SizeHints &hints = cachedItemSizeHints(index: i);
166 auto &flexLayoutItem = m_flexLayoutItems.at(i);
167 flexLayoutItem->setMinSize(hints.min());
168 if (flexLayoutItem->isFlexBasisUndefined()) {
169 // If flex basis is undefined and item is still stretched, it
170 // meant the flex child item has a const width or height but
171 // want to stretch vertically or horizontally
172 if (flexLayoutItem->isItemStreched()) {
173 if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(object: m_flexboxParentItem->quickItem())) {
174 // Reset the size of the child item if the parent sets
175 // its property 'align-item' to strecth
176 // Note: The child item can also override the parent
177 // align-item property through align-self
178 // (this is FlexboxLayout.alignItem for quick items)
179 flexLayoutItem->resetSize();
180 if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
181 parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) {
182 flexLayoutItem->setWidth(hints.pref().width());
183 } else {
184 flexLayoutItem->setHeight(hints.pref().height());
185 }
186 }
187 } else {
188 flexLayoutItem->setSize(hints.pref());
189 }
190 }
191 flexLayoutItem->setMaxSize(hints.max());
192 // The preferred size, minimum and maximum size of the parent item
193 // will be calculated as follows
194 // If no wrap enabled in the flex layout:
195 // For flex direction Row or RowReversed:
196 // Parent pref, min and max width:
197 // Sum of the pref, min and max width of the child
198 // items
199 // Parent pref, min and max height:
200 // Max of pref, min and max height of the child
201 // items
202 // For flex direction Column or ColumnReversed:
203 // Parent pref, min and max width:
204 // Max of pref, min and max width of the child
205 // items
206 // Parent pref, min and max height:
207 // Sum of the pref, min and max height of the
208 // child items
209 // Else if wrap enabled in the flex layout: (either Wrap or
210 // WrapReversed)
211 // For flex direction Row or RowReversed or Column or
212 // ColumnReversed:
213 // Parent pref, min, max width/height:
214 // Sum of the pref, min and max width/height of
215 // the child items
216 if (auto *qFlexLayout = qobject_cast<QQuickFlexboxLayout *>(object: m_flexboxParentItem->quickItem())) {
217 if (qFlexLayout->wrap() == QQuickFlexboxLayout::NoWrap) {
218 if (qFlexLayout->direction() == QQuickFlexboxLayout::Row ||
219 qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) {
220 // Minimum size
221 minS.setWidth(minS.width() + hints.min().width());
222 minS.setHeight(qMax(a: minS.height(), b: hints.min().height()));
223 // Preferred size
224 prefS.setWidth(prefS.width() + hints.pref().width());
225 prefS.setHeight(qMax(a: prefS.height(), b: hints.pref().height()));
226 // Maximum size
227 maxS.setWidth(maxS.width() + hints.max().width());
228 maxS.setHeight(qMax(a: maxS.height(), b: hints.max().height()));
229 } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column ||
230 qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) {
231 // Minimum size
232 minS.setWidth(qMax(a: minS.width(), b: hints.min().width()));
233 minS.setHeight(minS.height() + hints.min().height());
234 // Preferred size
235 prefS.setWidth(qMax(a: prefS.width(), b: hints.pref().width()));
236 prefS.setHeight(prefS.height() + hints.pref().height());
237 // Maximum size
238 maxS.setWidth(qMax(a: maxS.width(), b: hints.max().width()));
239 maxS.setHeight(maxS.height() + hints.max().height());
240 }
241 } else if (qFlexLayout->wrap() == QQuickFlexboxLayout::Wrap ||
242 qFlexLayout->wrap() == QQuickFlexboxLayout::WrapReverse) {
243 minS += hints.min();
244 prefS += hints.pref();
245 maxS += hints.max();
246 }
247 }
248 }
249 }
250 return askingFor;
251}
252
253void QQuickFlexboxLayoutEngine::invalidateItemSizeHint(QQuickItem *item)
254{
255 if (auto *flexLayoutItem = findFlexboxLayoutItem(item)) {
256 SizeHints &hints = flexLayoutItem->cachedItemSizeHints();
257 hints.min() = QSizeF();
258 hints.pref() = QSizeF();
259 hints.max() = QSizeF();
260 }
261}
262
263void QQuickFlexboxLayoutEngine::setGeometries(const QSizeF &contentSize)
264{
265 m_flexboxParentItem->setSize(contentSize);
266 m_flexboxParentItem->computeLayout(size: contentSize);
267 for (auto *item : m_flexLayoutItems) {
268 item->quickItem()->setPosition(item->position());
269 QSizeF oldSize = item->quickItem()->size();
270 QSizeF newSize = item->size();
271 if (oldSize == newSize) {
272 // Enforce rearrange as the size remains the same.
273 // This can happen in a case where we add a child item to the layout
274 // (which is already a child to a layout)
275 if (auto *layout = qobject_cast<QQuickLayout *>(object: item->quickItem())) {
276 if (layout->invalidatedArrangement())
277 layout->rearrange(newSize);
278 }
279 } else {
280 item->quickItem()->setSize(newSize);
281 }
282 }
283}
284
285// TODO: Need to check whether its needed to get the size of the flex item
286// through the callback measure function
287// QSizeF QQuickFlexboxLayoutItem::getSizeHint(float width,
288// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
289// {
290// QSizeF newSize(width, height);
291// switch (widthMode) {
292// case YGMeasureModeAtMost:
293// newSize.setWidth(m_cachedSizeHint.max().width());
294// break;
295// case YGMeasureModeExactly:
296// case YGMeasureModeUndefined:
297// newSize.setWidth(m_cachedSizeHint.pref().width());
298// break;
299// default: break;
300// }
301// switch (heightMode) {
302// case YGMeasureModeAtMost:
303// newSize.setHeight(m_cachedSizeHint.max().height());
304// break;
305// case YGMeasureModeExactly:
306// case YGMeasureModeUndefined:
307// newSize.setHeight(m_cachedSizeHint.pref().height());
308// break;
309// default: break;
310// }
311// return newSize;
312// }
313
314// YGSize QQuickFlexboxLayoutItem::measureFunc(YGNodeRef nodeRef, float width,
315// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
316// {
317// YGSize defaultSize;
318// auto *layoutItem = static_cast<QQuickFlexboxLayoutItem *>(YGNodeGetContext(nodeRef));
319// if (layoutItem) {
320// QSizeF size = layoutItem->getSizeHint(width, widthMode, height, heightMode);
321// defaultSize.width = (qt_is_nan(size.width()) || qt_is_inf(size.width())) ? YGUndefined : size.width();
322// defaultSize.height = (qt_is_nan(size.height()) || qt_is_inf(size.height())) ? YGUndefined : size.height();
323// }
324// return defaultSize;
325// }
326
327QT_END_NAMESPACE
328

source code of qtdeclarative/src/quicklayouts/qquickflexboxlayoutengine.cpp