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 // Set layout margins to the flex item (Layout.margins)
77 if (info->isMarginsSet())
78 flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeAll, info->margins());
79 if (info->isLeftMarginSet())
80 flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeLeft, info->leftMargin());
81 if (info->isRightMarginSet())
82 flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeRight, info->rightMargin());
83 if (info->isTopMarginSet())
84 flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeTop, info->topMargin());
85 if (info->isBottomMarginSet())
86 flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeBottom, info->bottomMargin());
87
88 // Set child item to grow, shrink and stretch depending on the layout
89 // properties.
90 // If Layout.fillWidth or Layout.fillHeight is set as true, then the child
91 // item within the layout can grow or shrink (considering the minimum and
92 // maximum sizes) along the main axis which depends upon the flex
93 // direction.
94 // If both Layout.fillWidth and Layout.fillHeight are set as true, then the
95 // child item within the layout need to grow or shrink in cross section and
96 // it require stretch need to be set for the yoga flex child item.
97 if (info->isFillWidthSet() || info->isFillHeightSet()) {
98 // Set stretch to child item both width and height
99 if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) {
100 const bool horz = parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
101 parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse;
102 if (horz) {
103 // If the Layout.fillHeight not been set, the preferred height
104 // will be set as height
105 if (!info->fillHeight())
106 flexItem->setHeight(sizeHints[Qt::PreferredSize].height());
107 flexItem->setFlexBasis(sizeHints[Qt::PreferredSize].width(), !info->fillWidth());
108 // Set child item to grow on main-axis (i.e. the flex
109 // direction)
110 flexItem->setItemGrowAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
111 // Set child item to shrink on main-axis (i.e. the flex
112 // direction)
113 flexItem->setItemShrinkAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
114 }
115 else {
116 // If the Layout.fillWidth not been set, the preferred width
117 // will be set as width
118 if (!info->fillWidth())
119 flexItem->setWidth(sizeHints[Qt::PreferredSize].width());
120 flexItem->setFlexBasis(sizeHints[Qt::PreferredSize].height(), !info->fillHeight());
121 // Set child item to grow on main-axis (i.e. the flex
122 // direction)
123 flexItem->setItemGrowAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
124 // Set child item to shrink on main-axis (i.e. the flex
125 // direction)
126 flexItem->setItemShrinkAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
127 }
128 // If the Layout.fillHeight not been set, the preferred height will be
129 // set as height in the previous condition. Otherwise (for
130 // Layout.fillHeight been set as true), make flex item to AlignStretch.
131 // Thus it can also grow vertically.
132 // Note: The same applies for Layout.fillWidth to grow horizontally.
133 if ((horz && (qt_is_nan(flexItem->size().height()) && info->fillHeight()))
134 || (!horz && (qt_is_nan(flexItem->size().width()) && info->fillWidth()))) {
135 flexItem->setItemStretchAlongCrossSection();
136 } else {
137 flexItem->inheritItemStretchAlongCrossSection();
138 }
139 }
140 }
141}
142
143SizeHints &QQuickFlexboxLayoutEngine::cachedItemSizeHints(int index) const
144{
145 QQuickFlexboxLayoutItem *flexBoxLayoutItem = m_flexLayoutItems.at(index);
146 Q_ASSERT(flexBoxLayoutItem);
147 SizeHints &hints = flexBoxLayoutItem->cachedItemSizeHints();
148 if (!hints.min().isValid())
149 collectItemSizeHints(flexBoxLayoutItem, hints.array);
150 return hints;
151}
152
153QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const
154{
155 QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
156 if (!askingFor.isValid()) {
157 QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
158 QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
159 QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
160
161 minS = QSizeF(0,0);
162 prefS = QSizeF(0,0);
163 maxS = QSizeF(0,0);
164
165 qreal flexColumnGap = 0;
166 qreal flexRowGap = 0;
167 bool wrap = false;
168 auto *qFlexLayout = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem());
169 if (qFlexLayout) {
170 flexColumnGap = qFlexLayout->columnGap();
171 flexRowGap = qFlexLayout->rowGap();
172 wrap = qFlexLayout->wrap() != QQuickFlexboxLayout::NoWrap;
173 }
174
175 const int count = itemCount();
176 for (int i = 0; i < count; ++i) {
177 SizeHints &hints = cachedItemSizeHints(i);
178 auto &flexLayoutItem = m_flexLayoutItems.at(i);
179 flexLayoutItem->setMinSize(hints.min());
180 if (flexLayoutItem->isFlexBasisUndefined()) {
181 // If flex basis is undefined and item is still stretched, it
182 // meant the flex child item has a const width or height but
183 // want to stretch vertically or horizontally
184 if (flexLayoutItem->isItemStreched()) {
185 if (qFlexLayout) {
186 // Reset the size of the child item if the parent sets
187 // its property 'align-item' to strecth
188 // Note: The child item can also override the parent
189 // align-item property through align-self
190 // (this is FlexboxLayout.alignItem for quick items)
191 flexLayoutItem->resetSize();
192 if (qFlexLayout->direction() == QQuickFlexboxLayout::Row ||
193 qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) {
194 flexLayoutItem->setWidth(hints.pref().width());
195 } else {
196 flexLayoutItem->setHeight(hints.pref().height());
197 }
198 }
199 } else {
200 flexLayoutItem->setSize(hints.pref());
201 }
202 }
203 flexLayoutItem->setMaxSize(hints.max());
204 // The preferred size, minimum and maximum size of the parent item
205 // will be calculated as follows
206 // If no wrap enabled in the flex layout:
207 // For flex direction Row or RowReversed:
208 // Parent pref, min and max width:
209 // Sum of the pref, min and max width of the child
210 // items
211 // Parent pref, min and max height:
212 // Max of pref, min and max height of the child
213 // items
214 // For flex direction Column or ColumnReversed:
215 // Parent pref, min and max width:
216 // Max of pref, min and max width of the child
217 // items
218 // Parent pref, min and max height:
219 // Sum of the pref, min and max height of the
220 // child items
221 // Else if wrap enabled in the flex layout: (either Wrap or
222 // WrapReversed)
223 // For flex direction Row or RowReversed or Column or
224 // ColumnReversed:
225 // Parent pref, min, max width/height:
226 // Sum of the pref, min and max width/height of
227 // the child items
228 if (qFlexLayout) {
229 const bool last = (i == count - 1);
230 if (qFlexLayout->direction() == QQuickFlexboxLayout::Row ||
231 qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) {
232 if (wrap) {
233 // Minimum size
234 minS.setWidth(qMax(minS.width(), hints.min().width()));
235 minS.setHeight(qMax(minS.height(), hints.min().height()));
236 // Preferred size. Same as no-wrap preferred size
237 prefS.setWidth(prefS.width() + hints.pref().width());
238 prefS.setHeight(qMax(prefS.height(), hints.pref().height()));
239 // Maximum size
240 maxS.setWidth(maxS.width() + hints.max().width());
241 maxS.setHeight(maxS.height() + hints.max().height());
242
243 if (!last) {
244 prefS.rwidth() += flexColumnGap;
245 maxS.rwidth() += flexColumnGap;
246 maxS.rheight() += flexRowGap;
247 }
248 } else {
249 // Minimum size
250 minS.setWidth(minS.width() + hints.min().width());
251 minS.setHeight(qMax(minS.height(), hints.min().height()));
252 // Preferred size
253 prefS.setWidth(prefS.width() + hints.pref().width());
254 prefS.setHeight(qMax(prefS.height(), hints.pref().height()));
255 // Maximum size
256 maxS.setWidth(maxS.width() + hints.max().width());
257 maxS.setHeight(qMax(maxS.height(), hints.max().height()));
258
259 if (!last) {
260 minS.rwidth() += flexColumnGap;
261 prefS.rwidth() += flexColumnGap;
262 maxS.rwidth() += flexColumnGap;
263 }
264 }
265
266 } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column ||
267 qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) {
268 if (wrap) {
269 // Minimum size
270 minS.setWidth(qMax(minS.width(), hints.min().width()));
271 minS.setHeight(qMax(minS.height(), hints.min().height()));
272 // Preferred size. Same as no-wrap preferred size
273 prefS.setWidth(qMax(prefS.width(), hints.pref().width()));
274 prefS.setHeight(prefS.height() + hints.pref().height());
275 // Maximum size
276 maxS.setWidth(maxS.width() + hints.max().width());
277 maxS.setHeight(maxS.height() + hints.max().height());
278
279 if (!last) {
280 prefS.rheight() += flexRowGap;
281 maxS.rwidth() += flexColumnGap;
282 maxS.rheight() += flexRowGap;
283 }
284 } else {
285 // Minimum size
286 minS.setWidth(qMax(minS.width(), hints.min().width()));
287 minS.setHeight(minS.height() + hints.min().height());
288 // Preferred size
289 prefS.setWidth(qMax(prefS.width(), hints.pref().width()));
290 prefS.setHeight(prefS.height() + hints.pref().height());
291 // Maximum size
292 maxS.setWidth(qMax(maxS.width(), hints.max().width()));
293 maxS.setHeight(maxS.height() + hints.max().height());
294
295 if (!last) {
296 minS.rheight() += flexRowGap;
297 prefS.rheight() += flexRowGap;
298 maxS.rheight() += flexRowGap;
299 }
300 }
301 }
302 }
303 }
304 }
305 return askingFor;
306}
307
308void QQuickFlexboxLayoutEngine::invalidateItemSizeHint(QQuickItem *item)
309{
310 if (auto *flexLayoutItem = findFlexboxLayoutItem(item)) {
311 SizeHints &hints = flexLayoutItem->cachedItemSizeHints();
312 hints.min() = QSizeF();
313 hints.pref() = QSizeF();
314 hints.max() = QSizeF();
315 }
316}
317
318void QQuickFlexboxLayoutEngine::setGeometries(const QSizeF &contentSize)
319{
320 m_flexboxParentItem->setSize(contentSize);
321 m_flexboxParentItem->computeLayout(contentSize);
322 for (auto *item : m_flexLayoutItems) {
323 item->quickItem()->setPosition(item->position());
324 QSizeF oldSize = item->quickItem()->size();
325 QSizeF newSize = item->size();
326 if (oldSize == newSize) {
327 // Enforce rearrange as the size remains the same.
328 // This can happen in a case where we add a child item to the layout
329 // (which is already a child to a layout)
330 if (auto *layout = qobject_cast<QQuickLayout *>(item->quickItem())) {
331 if (layout->invalidatedArrangement())
332 layout->rearrange(newSize);
333 }
334 } else {
335 item->quickItem()->setSize(newSize);
336 }
337 }
338}
339
340// TODO: Need to check whether its needed to get the size of the flex item
341// through the callback measure function
342// QSizeF QQuickFlexboxLayoutItem::getSizeHint(float width,
343// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
344// {
345// QSizeF newSize(width, height);
346// switch (widthMode) {
347// case YGMeasureModeAtMost:
348// newSize.setWidth(m_cachedSizeHint.max().width());
349// break;
350// case YGMeasureModeExactly:
351// case YGMeasureModeUndefined:
352// newSize.setWidth(m_cachedSizeHint.pref().width());
353// break;
354// default: break;
355// }
356// switch (heightMode) {
357// case YGMeasureModeAtMost:
358// newSize.setHeight(m_cachedSizeHint.max().height());
359// break;
360// case YGMeasureModeExactly:
361// case YGMeasureModeUndefined:
362// newSize.setHeight(m_cachedSizeHint.pref().height());
363// break;
364// default: break;
365// }
366// return newSize;
367// }
368
369// YGSize QQuickFlexboxLayoutItem::measureFunc(YGNodeRef nodeRef, float width,
370// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
371// {
372// YGSize defaultSize;
373// auto *layoutItem = static_cast<QQuickFlexboxLayoutItem *>(YGNodeGetContext(nodeRef));
374// if (layoutItem) {
375// QSizeF size = layoutItem->getSizeHint(width, widthMode, height, heightMode);
376// defaultSize.width = (qt_is_nan(size.width()) || qt_is_inf(size.width())) ? YGUndefined : size.width();
377// defaultSize.height = (qt_is_nan(size.height()) || qt_is_inf(size.height())) ? YGUndefined : size.height();
378// }
379// return defaultSize;
380// }
381
382QT_END_NAMESPACE