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 if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
101 parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) {
102 // If the Layout.fillHeight not been set, the preferred height
103 // will be set as height
104 if (!info->fillHeight())
105 flexItem->setHeight(sizeHints[Qt::PreferredSize].height());
106 flexItem->setFlexBasis(sizeHints[Qt::PreferredSize].width(), !info->fillWidth());
107 // Set child item to grow on main-axis (i.e. the flex
108 // direction)
109 flexItem->setItemGrowAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
110 // Set child item to shrink on main-axis (i.e. the flex
111 // direction)
112 flexItem->setItemShrinkAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
113 }
114 else {
115 // If the Layout.fillWidth not been set, the preferred width
116 // will be set as width
117 if (!info->fillWidth())
118 flexItem->setWidth(sizeHints[Qt::PreferredSize].width());
119 flexItem->setFlexBasis(sizeHints[Qt::PreferredSize].height(), !info->fillHeight());
120 // Set child item to grow on main-axis (i.e. the flex
121 // direction)
122 flexItem->setItemGrowAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
123 // Set child item to shrink on main-axis (i.e. the flex
124 // direction)
125 flexItem->setItemShrinkAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
126 }
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 ((qt_is_nan(flexItem->size().width()) && info->fillWidth()) ||
134 (qt_is_nan(flexItem->size().height()) && info->fillHeight())) {
135 flexItem->setItemStretchAlongCrossSection();
136 } else {
137 flexItem->inheritItemStretchAlongCrossSection();
138 }
139 }
140}
141
142SizeHints &QQuickFlexboxLayoutEngine::cachedItemSizeHints(int index) const
143{
144 QQuickFlexboxLayoutItem *flexBoxLayoutItem = m_flexLayoutItems.at(index);
145 Q_ASSERT(flexBoxLayoutItem);
146 SizeHints &hints = flexBoxLayoutItem->cachedItemSizeHints();
147 if (!hints.min().isValid())
148 collectItemSizeHints(flexBoxLayoutItem, hints.array);
149 return hints;
150}
151
152QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const
153{
154 QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
155 if (!askingFor.isValid()) {
156 QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
157 QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
158 QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
159
160 minS = QSizeF(0,0);
161 prefS = QSizeF(0,0);
162 maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity());
163
164 const int count = itemCount();
165 for (int i = 0; i < count; ++i) {
166 SizeHints &hints = cachedItemSizeHints(i);
167 auto &flexLayoutItem = m_flexLayoutItems.at(i);
168 flexLayoutItem->setMinSize(hints.min());
169 if (flexLayoutItem->isFlexBasisUndefined()) {
170 // If flex basis is undefined and item is still stretched, it
171 // meant the flex child item has a const width or height but
172 // want to stretch vertically or horizontally
173 if (flexLayoutItem->isItemStreched()) {
174 if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) {
175 // Reset the size of the child item if the parent sets
176 // its property 'align-item' to strecth
177 // Note: The child item can also override the parent
178 // align-item property through align-self
179 // (this is FlexboxLayout.alignItem for quick items)
180 flexLayoutItem->resetSize();
181 if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
182 parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) {
183 flexLayoutItem->setWidth(hints.pref().width());
184 } else {
185 flexLayoutItem->setHeight(hints.pref().height());
186 }
187 }
188 } else {
189 flexLayoutItem->setSize(hints.pref());
190 }
191 }
192 flexLayoutItem->setMaxSize(hints.max());
193 // The preferred size, minimum and maximum size of the parent item
194 // will be calculated as follows
195 // If no wrap enabled in the flex layout:
196 // For flex direction Row or RowReversed:
197 // Parent pref, min and max width:
198 // Sum of the pref, min and max width of the child
199 // items
200 // Parent pref, min and max height:
201 // Max of pref, min and max height of the child
202 // items
203 // For flex direction Column or ColumnReversed:
204 // Parent pref, min and max width:
205 // Max of pref, min and max width of the child
206 // items
207 // Parent pref, min and max height:
208 // Sum of the pref, min and max height of the
209 // child items
210 // Else if wrap enabled in the flex layout: (either Wrap or
211 // WrapReversed)
212 // For flex direction Row or RowReversed or Column or
213 // ColumnReversed:
214 // Parent pref, min, max width/height:
215 // Sum of the pref, min and max width/height of
216 // the child items
217 if (auto *qFlexLayout = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) {
218 if (qFlexLayout->wrap() == QQuickFlexboxLayout::NoWrap) {
219 if (qFlexLayout->direction() == QQuickFlexboxLayout::Row ||
220 qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) {
221 // Minimum size
222 minS.setWidth(minS.width() + hints.min().width());
223 minS.setHeight(qMax(minS.height(), hints.min().height()));
224 // Preferred size
225 prefS.setWidth(prefS.width() + hints.pref().width());
226 prefS.setHeight(qMax(prefS.height(), hints.pref().height()));
227 // Maximum size
228 maxS.setWidth(maxS.width() + hints.max().width());
229 maxS.setHeight(qMax(maxS.height(), hints.max().height()));
230 } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column ||
231 qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) {
232 // Minimum size
233 minS.setWidth(qMax(minS.width(), hints.min().width()));
234 minS.setHeight(minS.height() + hints.min().height());
235 // Preferred size
236 prefS.setWidth(qMax(prefS.width(), hints.pref().width()));
237 prefS.setHeight(prefS.height() + hints.pref().height());
238 // Maximum size
239 maxS.setWidth(qMax(maxS.width(), hints.max().width()));
240 maxS.setHeight(maxS.height() + hints.max().height());
241 }
242 } else if (qFlexLayout->wrap() == QQuickFlexboxLayout::Wrap ||
243 qFlexLayout->wrap() == QQuickFlexboxLayout::WrapReverse) {
244 minS += hints.min();
245 prefS += hints.pref();
246 maxS += hints.max();
247 }
248 }
249 }
250 }
251 return askingFor;
252}
253
254void QQuickFlexboxLayoutEngine::invalidateItemSizeHint(QQuickItem *item)
255{
256 if (auto *flexLayoutItem = findFlexboxLayoutItem(item)) {
257 SizeHints &hints = flexLayoutItem->cachedItemSizeHints();
258 hints.min() = QSizeF();
259 hints.pref() = QSizeF();
260 hints.max() = QSizeF();
261 }
262}
263
264void QQuickFlexboxLayoutEngine::setGeometries(const QSizeF &contentSize)
265{
266 m_flexboxParentItem->setSize(contentSize);
267 m_flexboxParentItem->computeLayout(contentSize);
268 for (auto *item : m_flexLayoutItems) {
269 item->quickItem()->setPosition(item->position());
270 QSizeF oldSize = item->quickItem()->size();
271 QSizeF newSize = item->size();
272 if (oldSize == newSize) {
273 // Enforce rearrange as the size remains the same.
274 // This can happen in a case where we add a child item to the layout
275 // (which is already a child to a layout)
276 if (auto *layout = qobject_cast<QQuickLayout *>(item->quickItem())) {
277 if (layout->invalidatedArrangement())
278 layout->rearrange(newSize);
279 }
280 } else {
281 item->quickItem()->setSize(newSize);
282 }
283 }
284}
285
286// TODO: Need to check whether its needed to get the size of the flex item
287// through the callback measure function
288// QSizeF QQuickFlexboxLayoutItem::getSizeHint(float width,
289// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
290// {
291// QSizeF newSize(width, height);
292// switch (widthMode) {
293// case YGMeasureModeAtMost:
294// newSize.setWidth(m_cachedSizeHint.max().width());
295// break;
296// case YGMeasureModeExactly:
297// case YGMeasureModeUndefined:
298// newSize.setWidth(m_cachedSizeHint.pref().width());
299// break;
300// default: break;
301// }
302// switch (heightMode) {
303// case YGMeasureModeAtMost:
304// newSize.setHeight(m_cachedSizeHint.max().height());
305// break;
306// case YGMeasureModeExactly:
307// case YGMeasureModeUndefined:
308// newSize.setHeight(m_cachedSizeHint.pref().height());
309// break;
310// default: break;
311// }
312// return newSize;
313// }
314
315// YGSize QQuickFlexboxLayoutItem::measureFunc(YGNodeRef nodeRef, float width,
316// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
317// {
318// YGSize defaultSize;
319// auto *layoutItem = static_cast<QQuickFlexboxLayoutItem *>(YGNodeGetContext(nodeRef));
320// if (layoutItem) {
321// QSizeF size = layoutItem->getSizeHint(width, widthMode, height, heightMode);
322// defaultSize.width = (qt_is_nan(size.width()) || qt_is_inf(size.width())) ? YGUndefined : size.width();
323// defaultSize.height = (qt_is_nan(size.height()) || qt_is_inf(size.height())) ? YGUndefined : size.height();
324// }
325// return defaultSize;
326// }
327
328QT_END_NAMESPACE