1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Quick Layouts module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qquickstacklayout_p.h" |
41 | #include <limits> |
42 | |
43 | /*! |
44 | \qmltype StackLayout |
45 | \instantiates QQuickStackLayout |
46 | \inherits Item |
47 | \inqmlmodule QtQuick.Layouts |
48 | \ingroup layouts |
49 | \brief The StackLayout class provides a stack of items where |
50 | only one item is visible at a time. |
51 | |
52 | The current visible item can be modified by setting the \l currentIndex property. |
53 | The index corresponds to the order of the StackLayout's children. |
54 | |
55 | In contrast to most other layouts, child Items' \l{Layout::fillWidth}{Layout.fillWidth} and \l{Layout::fillHeight}{Layout.fillHeight} properties |
56 | default to \c true. As a consequence, child items are by default filled to match the size of the StackLayout as long as their |
57 | \l{Layout::maximumWidth}{Layout.maximumWidth} or \l{Layout::maximumHeight}{Layout.maximumHeight} does not prevent it. |
58 | |
59 | Items are added to the layout by reparenting the item to the layout. Similarly, removal is done by reparenting the item from the layout. |
60 | Both of these operations will affect the layout's \l count property. |
61 | |
62 | The following code will create a StackLayout where only the 'plum' rectangle is visible. |
63 | \code |
64 | StackLayout { |
65 | id: layout |
66 | anchors.fill: parent |
67 | currentIndex: 1 |
68 | Rectangle { |
69 | color: 'teal' |
70 | implicitWidth: 200 |
71 | implicitHeight: 200 |
72 | } |
73 | Rectangle { |
74 | color: 'plum' |
75 | implicitWidth: 300 |
76 | implicitHeight: 200 |
77 | } |
78 | } |
79 | \endcode |
80 | |
81 | Items in a StackLayout support these attached properties: |
82 | \list |
83 | \li \l{Layout::minimumWidth}{Layout.minimumWidth} |
84 | \li \l{Layout::minimumHeight}{Layout.minimumHeight} |
85 | \li \l{Layout::preferredWidth}{Layout.preferredWidth} |
86 | \li \l{Layout::preferredHeight}{Layout.preferredHeight} |
87 | \li \l{Layout::maximumWidth}{Layout.maximumWidth} |
88 | \li \l{Layout::maximumHeight}{Layout.maximumHeight} |
89 | \li \l{Layout::fillWidth}{Layout.fillWidth} |
90 | \li \l{Layout::fillHeight}{Layout.fillHeight} |
91 | \endlist |
92 | |
93 | Read more about attached properties \l{QML Object Attributes}{here}. |
94 | \sa ColumnLayout |
95 | \sa GridLayout |
96 | \sa RowLayout |
97 | \sa {QtQuick.Controls::StackView}{StackView} |
98 | */ |
99 | |
100 | QT_BEGIN_NAMESPACE |
101 | |
102 | QQuickStackLayout::QQuickStackLayout(QQuickItem *parent) : |
103 | QQuickLayout(*new QQuickStackLayoutPrivate, parent) |
104 | { |
105 | } |
106 | |
107 | /*! |
108 | \qmlproperty int StackLayout::count |
109 | |
110 | This property holds the number of items that belong to the layout. |
111 | |
112 | Only items that are children of the StackLayout will be candidates for layouting. |
113 | */ |
114 | int QQuickStackLayout::count() const |
115 | { |
116 | Q_D(const QQuickStackLayout); |
117 | ensureLayoutItemsUpdated(); |
118 | return d->count; |
119 | } |
120 | |
121 | /*! |
122 | \qmlproperty int StackLayout::currentIndex |
123 | |
124 | This property holds the index of the child item that is currently visible in the StackLayout. |
125 | By default it will be \c -1 for an empty layout, otherwise the default is \c 0 (referring to the first item). |
126 | */ |
127 | int QQuickStackLayout::currentIndex() const |
128 | { |
129 | Q_D(const QQuickStackLayout); |
130 | ensureLayoutItemsUpdated(); |
131 | return d->currentIndex; |
132 | } |
133 | |
134 | void QQuickStackLayout::setCurrentIndex(int index) |
135 | { |
136 | Q_D(QQuickStackLayout); |
137 | if (index != d->currentIndex) { |
138 | ensureLayoutItemsUpdated(); |
139 | QQuickItem *prev = itemAt(index: d->currentIndex); |
140 | QQuickItem *next = itemAt(index); |
141 | d->currentIndex = index; |
142 | d->explicitCurrentIndex = true; |
143 | if (prev) |
144 | prev->setVisible(false); |
145 | if (next) |
146 | next->setVisible(true); |
147 | |
148 | if (isComponentComplete()) { |
149 | rearrange(QSizeF(width(), height())); |
150 | emit currentIndexChanged(); |
151 | } |
152 | } |
153 | } |
154 | |
155 | |
156 | void QQuickStackLayout::componentComplete() |
157 | { |
158 | QQuickLayout::componentComplete(); // will call our geometryChange(), (where isComponentComplete() == true) |
159 | |
160 | ensureLayoutItemsUpdated(); |
161 | |
162 | QQuickItem *par = parentItem(); |
163 | if (qobject_cast<QQuickLayout*>(object: par)) |
164 | return; |
165 | |
166 | rearrange(QSizeF(width(), height())); |
167 | } |
168 | |
169 | void QQuickStackLayout::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) |
170 | { |
171 | QQuickLayout::itemChange(change, value); |
172 | |
173 | if (change == ItemChildRemovedChange) { |
174 | invalidate(); |
175 | } else if (change == ItemChildAddedChange) { |
176 | invalidate(); |
177 | } |
178 | } |
179 | |
180 | QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const |
181 | { |
182 | Q_D(const QQuickStackLayout); |
183 | ensureLayoutItemsUpdated(); |
184 | QSizeF &askingFor = m_cachedSizeHints[whichSizeHint]; |
185 | if (!askingFor.isValid()) { |
186 | QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize]; |
187 | QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize]; |
188 | QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize]; |
189 | |
190 | minS = QSizeF(0,0); |
191 | prefS = QSizeF(0,0); |
192 | maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity()); |
193 | |
194 | const int count = itemCount(); |
195 | m_cachedItemSizeHints.resize(size: count); |
196 | for (int i = 0; i < count; ++i) { |
197 | SizeHints &hints = m_cachedItemSizeHints[i]; |
198 | QQuickStackLayout::collectItemSizeHints(item: itemAt(index: i), sizeHints: hints.array); |
199 | minS = minS.expandedTo(otherSize: hints.min()); |
200 | prefS = prefS.expandedTo(otherSize: hints.pref()); |
201 | //maxS = maxS.boundedTo(hints.max()); // Can be resized to be larger than any of its items. |
202 | // This is the same as QStackLayout does it. |
203 | // Not sure how descent makes sense here... |
204 | } |
205 | } |
206 | d->m_dirty = false; |
207 | return askingFor; |
208 | } |
209 | |
210 | int QQuickStackLayout::indexOf(QQuickItem *childItem) const |
211 | { |
212 | ensureLayoutItemsUpdated(); |
213 | if (childItem) { |
214 | int indexOfItem = 0; |
215 | const auto items = childItems(); |
216 | for (QQuickItem *item : items) { |
217 | if (shouldIgnoreItem(item)) |
218 | continue; |
219 | if (childItem == item) |
220 | return indexOfItem; |
221 | ++indexOfItem; |
222 | } |
223 | } |
224 | return -1; |
225 | } |
226 | |
227 | QQuickItem *QQuickStackLayout::itemAt(int index) const |
228 | { |
229 | const auto items = childItems(); |
230 | for (QQuickItem *item : items) { |
231 | if (shouldIgnoreItem(item)) |
232 | continue; |
233 | if (index == 0) |
234 | return item; |
235 | --index; |
236 | } |
237 | return nullptr; |
238 | } |
239 | |
240 | int QQuickStackLayout::itemCount() const |
241 | { |
242 | int count = 0; |
243 | const auto items = childItems(); |
244 | for (QQuickItem *item : items) { |
245 | if (shouldIgnoreItem(item)) |
246 | continue; |
247 | ++count; |
248 | } |
249 | return count; |
250 | } |
251 | |
252 | void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*align*/) |
253 | { |
254 | // ### Do we have to respect alignment? |
255 | } |
256 | |
257 | void QQuickStackLayout::invalidate(QQuickItem *childItem) |
258 | { |
259 | const int indexOfChild = indexOf(childItem); |
260 | if (indexOfChild >= 0 && indexOfChild < m_cachedItemSizeHints.count()) { |
261 | m_cachedItemSizeHints[indexOfChild].min() = QSizeF(); |
262 | m_cachedItemSizeHints[indexOfChild].pref() = QSizeF(); |
263 | m_cachedItemSizeHints[indexOfChild].max() = QSizeF(); |
264 | } |
265 | |
266 | for (int i = 0; i < Qt::NSizeHints; ++i) |
267 | m_cachedSizeHints[i] = QSizeF(); |
268 | QQuickLayout::invalidate(childItem: this); |
269 | |
270 | if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(object: parentItem())) |
271 | parentLayout->invalidate(childItem: this); |
272 | } |
273 | |
274 | void QQuickStackLayout::updateLayoutItems() |
275 | { |
276 | Q_D(QQuickStackLayout); |
277 | d->m_ignoredItems.clear(); |
278 | const int count = itemCount(); |
279 | int oldIndex = d->currentIndex; |
280 | if (!d->explicitCurrentIndex) |
281 | d->currentIndex = (count > 0 ? 0 : -1); |
282 | |
283 | if (d->currentIndex != oldIndex) |
284 | emit currentIndexChanged(); |
285 | |
286 | if (count != d->count) { |
287 | d->count = count; |
288 | emit countChanged(); |
289 | } |
290 | for (int i = 0; i < count; ++i) { |
291 | QQuickItem *child = itemAt(index: i); |
292 | checkAnchors(item: child); |
293 | child->setVisible(d->currentIndex == i); |
294 | } |
295 | } |
296 | |
297 | void QQuickStackLayout::rearrange(const QSizeF &newSize) |
298 | { |
299 | Q_D(QQuickStackLayout); |
300 | if (newSize.isNull() || !newSize.isValid()) |
301 | return; |
302 | |
303 | qCDebug(lcQuickLayouts) << "QQuickStackLayout::rearrange" ; |
304 | ensureLayoutItemsUpdated(); |
305 | |
306 | if (d->currentIndex == -1 || d->currentIndex >= m_cachedItemSizeHints.count()) |
307 | return; |
308 | QQuickStackLayout::SizeHints &hints = m_cachedItemSizeHints[d->currentIndex]; |
309 | QQuickItem *item = itemAt(index: d->currentIndex); |
310 | Q_ASSERT(item); |
311 | item->setPosition(QPointF(0,0)); // ### respect alignment? |
312 | const QSizeF oldSize(item->width(), item->height()); |
313 | const QSizeF effectiveNewSize = newSize.expandedTo(otherSize: hints.min()).boundedTo(otherSize: hints.max()); |
314 | item->setSize(effectiveNewSize); |
315 | if (effectiveNewSize == oldSize) |
316 | item->polish(); |
317 | QQuickLayout::rearrange(newSize); |
318 | } |
319 | |
320 | void QQuickStackLayout::collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints) |
321 | { |
322 | QQuickLayoutAttached *info = nullptr; |
323 | QQuickLayout::effectiveSizeHints_helper(item, cachedSizeHints: sizeHints, info: &info, useFallbackToWidthOrHeight: true); |
324 | if (!info) |
325 | return; |
326 | if (info->isFillWidthSet() && !info->fillWidth()) { |
327 | const qreal pref = sizeHints[Qt::PreferredSize].width(); |
328 | sizeHints[Qt::MinimumSize].setWidth(pref); |
329 | sizeHints[Qt::MaximumSize].setWidth(pref); |
330 | } |
331 | |
332 | if (info->isFillHeightSet() && !info->fillHeight()) { |
333 | const qreal pref = sizeHints[Qt::PreferredSize].height(); |
334 | sizeHints[Qt::MinimumSize].setHeight(pref); |
335 | sizeHints[Qt::MaximumSize].setHeight(pref); |
336 | } |
337 | } |
338 | |
339 | bool QQuickStackLayout::shouldIgnoreItem(QQuickItem *item) const |
340 | { |
341 | const bool ignored = QQuickItemPrivate::get(item)->isTransparentForPositioner(); |
342 | if (ignored) |
343 | d_func()->m_ignoredItems << item; |
344 | return ignored; |
345 | } |
346 | |
347 | QT_END_NAMESPACE |
348 | |
349 | #include "moc_qquickstacklayout_p.cpp" |
350 | |