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