1 | // Copyright (C) 2017 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 "qquickswipeview_p.h" |
5 | |
6 | #include <QtQml/qqmlinfo.h> |
7 | #include <QtQuickTemplates2/private/qquickcontainer_p_p.h> |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | /*! |
12 | \qmltype SwipeView |
13 | \inherits Container |
14 | //! \instantiates QQuickSwipeView |
15 | \inqmlmodule QtQuick.Controls |
16 | \since 5.7 |
17 | \ingroup qtquickcontrols-navigation |
18 | \ingroup qtquickcontrols-containers |
19 | \ingroup qtquickcontrols-focusscopes |
20 | \brief Enables the user to navigate pages by swiping sideways. |
21 | |
22 | SwipeView provides a swipe-based navigation model. |
23 | |
24 | \image qtquickcontrols-swipeview.gif |
25 | |
26 | SwipeView is populated with a set of pages. One page is visible at a time. |
27 | The user can navigate between the pages by swiping sideways. Notice that |
28 | SwipeView itself is entirely non-visual. It is recommended to combine it |
29 | with PageIndicator, to give the user a visual clue that there are multiple |
30 | pages. |
31 | |
32 | \snippet qtquickcontrols-swipeview-indicator.qml 1 |
33 | |
34 | As shown above, SwipeView is typically populated with a static set of |
35 | pages that are defined inline as children of the view. It is also possible |
36 | to \l {Container::addItem()}{add}, \l {Container::insertItem()}{insert}, |
37 | \l {Container::moveItem()}{move}, and \l {Container::removeItem()}{remove} |
38 | pages dynamically at run time. |
39 | |
40 | \include container-currentindex.qdocinc {file} {SwipeView} {TabBar} |
41 | |
42 | It is generally not advisable to add excessive amounts of pages to a |
43 | SwipeView. However, when the amount of pages grows larger, or individual |
44 | pages are relatively complex, it may be desirable to free up resources by |
45 | unloading pages that are outside the immediate reach of the user. |
46 | The following example presents how to use \l Loader to keep a maximum of |
47 | three pages simultaneously instantiated. |
48 | |
49 | \code |
50 | SwipeView { |
51 | Repeater { |
52 | model: 6 |
53 | Loader { |
54 | active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem |
55 | sourceComponent: Text { |
56 | text: index |
57 | Component.onCompleted: console.log("created:", index) |
58 | Component.onDestruction: console.log("destroyed:", index) |
59 | } |
60 | } |
61 | } |
62 | } |
63 | \endcode |
64 | |
65 | \note SwipeView takes over the geometry management of items added to the |
66 | view. Using anchors on the items is not supported, and any \c width |
67 | or \c height assignment will be overridden by the view. Notice that |
68 | this only applies to the root of the item. Specifying width and height, |
69 | or using anchors for its children works as expected. |
70 | |
71 | \sa TabBar, PageIndicator, {Customizing SwipeView}, {Navigation Controls}, {Container Controls}, |
72 | {Focus Management in Qt Quick Controls} |
73 | */ |
74 | |
75 | class QQuickSwipeViewPrivate : public QQuickContainerPrivate |
76 | { |
77 | Q_DECLARE_PUBLIC(QQuickSwipeView) |
78 | |
79 | public: |
80 | void resizeItem(int index, QQuickItem *item); |
81 | void resizeItems(); |
82 | |
83 | static QQuickSwipeViewPrivate *get(QQuickSwipeView *view); |
84 | |
85 | void itemImplicitWidthChanged(QQuickItem *item) override; |
86 | void itemImplicitHeightChanged(QQuickItem *item) override; |
87 | |
88 | qreal getContentWidth() const override; |
89 | qreal getContentHeight() const override; |
90 | |
91 | bool interactive = true; |
92 | Qt::Orientation orientation = Qt::Horizontal; |
93 | }; |
94 | |
95 | class QQuickSwipeViewAttachedPrivate : public QObjectPrivate |
96 | { |
97 | public: |
98 | Q_DECLARE_PUBLIC(QQuickSwipeViewAttached) |
99 | |
100 | static QQuickSwipeViewAttachedPrivate *get(QQuickSwipeViewAttached *attached) |
101 | { |
102 | return attached->d_func(); |
103 | } |
104 | |
105 | void update(QQuickSwipeView *newView, int newIndex); |
106 | void updateCurrentIndex(); |
107 | void setCurrentIndex(int i); |
108 | |
109 | QQuickSwipeView *swipeView = nullptr; |
110 | int index = -1; |
111 | int currentIndex = -1; |
112 | }; |
113 | |
114 | void QQuickSwipeViewPrivate::resizeItem(int index, QQuickItem *item) |
115 | { |
116 | QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; |
117 | // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors |
118 | if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property(name: "_q_QQuickSwipeView_warned" ).toBool()) { |
119 | qmlWarning(me: item) << "SwipeView has detected conflicting anchors. Unable to layout the item." ; |
120 | item->setProperty(name: "_q_QQuickSwipeView_warned" , value: true); |
121 | } |
122 | if (orientation == Qt::Horizontal) |
123 | item->setPosition({index * (contentItem->width() + spacing), 0}); |
124 | else |
125 | item->setPosition({0, index * (contentItem->height() + spacing)}); |
126 | item->setSize(QSizeF(contentItem->width(), contentItem->height())); |
127 | } |
128 | |
129 | void QQuickSwipeViewPrivate::resizeItems() |
130 | { |
131 | Q_Q(QQuickSwipeView); |
132 | const int count = q->count(); |
133 | for (int i = 0; i < count; ++i) { |
134 | QQuickItem *item = itemAt(index: i); |
135 | if (item) |
136 | resizeItem(index: i, item); |
137 | } |
138 | } |
139 | |
140 | QQuickSwipeViewPrivate *QQuickSwipeViewPrivate::get(QQuickSwipeView *view) |
141 | { |
142 | return view->d_func(); |
143 | } |
144 | |
145 | void QQuickSwipeViewPrivate::itemImplicitWidthChanged(QQuickItem *item) |
146 | { |
147 | Q_Q(QQuickSwipeView); |
148 | QQuickContainerPrivate::itemImplicitWidthChanged(item); |
149 | if (item == q->currentItem()) |
150 | updateImplicitContentWidth(); |
151 | } |
152 | |
153 | void QQuickSwipeViewPrivate::itemImplicitHeightChanged(QQuickItem *item) |
154 | { |
155 | Q_Q(QQuickSwipeView); |
156 | QQuickContainerPrivate::itemImplicitHeightChanged(item); |
157 | if (item == q->currentItem()) |
158 | updateImplicitContentHeight(); |
159 | } |
160 | |
161 | qreal QQuickSwipeViewPrivate::getContentWidth() const |
162 | { |
163 | Q_Q(const QQuickSwipeView); |
164 | QQuickItem *currentItem = q->currentItem(); |
165 | return currentItem ? currentItem->implicitWidth() : 0; |
166 | } |
167 | |
168 | qreal QQuickSwipeViewPrivate::getContentHeight() const |
169 | { |
170 | Q_Q(const QQuickSwipeView); |
171 | QQuickItem *currentItem = q->currentItem(); |
172 | return currentItem ? currentItem->implicitHeight() : 0; |
173 | } |
174 | |
175 | QQuickSwipeView::QQuickSwipeView(QQuickItem *parent) |
176 | : QQuickContainer(*(new QQuickSwipeViewPrivate), parent) |
177 | { |
178 | Q_D(QQuickSwipeView); |
179 | d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; |
180 | setFlag(flag: ItemIsFocusScope); |
181 | setActiveFocusOnTab(true); |
182 | QObjectPrivate::connect(sender: this, signal: &QQuickContainer::currentItemChanged, receiverPrivate: d, slot: &QQuickControlPrivate::updateImplicitContentSize); |
183 | } |
184 | |
185 | /*! |
186 | \since QtQuick.Controls 2.1 (Qt 5.8) |
187 | \qmlproperty bool QtQuick.Controls::SwipeView::interactive |
188 | |
189 | This property describes whether the user can interact with the SwipeView. |
190 | The user cannot swipe a view that is not interactive. |
191 | |
192 | The default value is \c true. |
193 | */ |
194 | bool QQuickSwipeView::isInteractive() const |
195 | { |
196 | Q_D(const QQuickSwipeView); |
197 | return d->interactive; |
198 | } |
199 | |
200 | void QQuickSwipeView::setInteractive(bool interactive) |
201 | { |
202 | Q_D(QQuickSwipeView); |
203 | if (d->interactive == interactive) |
204 | return; |
205 | |
206 | d->interactive = interactive; |
207 | emit interactiveChanged(); |
208 | } |
209 | |
210 | /*! |
211 | \since QtQuick.Controls 2.2 (Qt 5.9) |
212 | \qmlproperty enumeration QtQuick.Controls::SwipeView::orientation |
213 | |
214 | This property holds the orientation. |
215 | |
216 | Possible values: |
217 | \value Qt.Horizontal Horizontal (default) |
218 | \value Qt.Vertical Vertical |
219 | |
220 | \sa horizontal, vertical |
221 | */ |
222 | Qt::Orientation QQuickSwipeView::orientation() const |
223 | { |
224 | Q_D(const QQuickSwipeView); |
225 | return d->orientation; |
226 | } |
227 | |
228 | void QQuickSwipeView::setOrientation(Qt::Orientation orientation) |
229 | { |
230 | Q_D(QQuickSwipeView); |
231 | if (d->orientation == orientation) |
232 | return; |
233 | |
234 | d->orientation = orientation; |
235 | if (isComponentComplete()) |
236 | d->resizeItems(); |
237 | emit orientationChanged(); |
238 | } |
239 | |
240 | /*! |
241 | \since QtQuick.Controls 2.3 (Qt 5.10) |
242 | \qmlproperty bool QtQuick.Controls::SwipeView::horizontal |
243 | \readonly |
244 | |
245 | This property holds whether the swipe view is horizontal. |
246 | |
247 | \sa orientation |
248 | */ |
249 | bool QQuickSwipeView::isHorizontal() const |
250 | { |
251 | Q_D(const QQuickSwipeView); |
252 | return d->orientation == Qt::Horizontal; |
253 | } |
254 | |
255 | /*! |
256 | \since QtQuick.Controls 2.3 (Qt 5.10) |
257 | \qmlproperty bool QtQuick.Controls::SwipeView::vertical |
258 | \readonly |
259 | |
260 | This property holds whether the swipe view is vertical. |
261 | |
262 | \sa orientation |
263 | */ |
264 | bool QQuickSwipeView::isVertical() const |
265 | { |
266 | Q_D(const QQuickSwipeView); |
267 | return d->orientation == Qt::Vertical; |
268 | } |
269 | |
270 | QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object) |
271 | { |
272 | return new QQuickSwipeViewAttached(object); |
273 | } |
274 | |
275 | void QQuickSwipeView::componentComplete() |
276 | { |
277 | Q_D(QQuickSwipeView); |
278 | QQuickContainer::componentComplete(); |
279 | d->resizeItems(); |
280 | } |
281 | |
282 | void QQuickSwipeView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
283 | { |
284 | Q_D(QQuickSwipeView); |
285 | QQuickContainer::geometryChange(newGeometry, oldGeometry); |
286 | d->resizeItems(); |
287 | } |
288 | |
289 | void QQuickSwipeView::itemAdded(int index, QQuickItem *item) |
290 | { |
291 | Q_D(QQuickSwipeView); |
292 | if (isComponentComplete()) |
293 | d->resizeItem(index, item); |
294 | QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(object: qmlAttachedPropertiesObject<QQuickSwipeView>(obj: item)); |
295 | if (attached) |
296 | QQuickSwipeViewAttachedPrivate::get(attached)->update(newView: this, newIndex: index); |
297 | } |
298 | |
299 | void QQuickSwipeView::itemMoved(int index, QQuickItem *item) |
300 | { |
301 | QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(object: qmlAttachedPropertiesObject<QQuickSwipeView>(obj: item)); |
302 | if (attached) |
303 | QQuickSwipeViewAttachedPrivate::get(attached)->update(newView: this, newIndex: index); |
304 | } |
305 | |
306 | void QQuickSwipeView::itemRemoved(int, QQuickItem *item) |
307 | { |
308 | QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(object: qmlAttachedPropertiesObject<QQuickSwipeView>(obj: item)); |
309 | if (attached) |
310 | QQuickSwipeViewAttachedPrivate::get(attached)->update(newView: nullptr, newIndex: -1); |
311 | } |
312 | |
313 | #if QT_CONFIG(accessibility) |
314 | QAccessible::Role QQuickSwipeView::accessibleRole() const |
315 | { |
316 | return QAccessible::PageTabList; |
317 | } |
318 | #endif |
319 | |
320 | /*! |
321 | \qmlattachedproperty int QtQuick.Controls::SwipeView::index |
322 | \readonly |
323 | |
324 | This attached property holds the index of each child item in the SwipeView. |
325 | |
326 | It is attached to each child item of the SwipeView. |
327 | */ |
328 | |
329 | /*! |
330 | \qmlattachedproperty bool QtQuick.Controls::SwipeView::isCurrentItem |
331 | \readonly |
332 | |
333 | This attached property is \c true if this child is the current item. |
334 | |
335 | It is attached to each child item of the SwipeView. |
336 | */ |
337 | |
338 | /*! |
339 | \qmlattachedproperty bool QtQuick.Controls::SwipeView::isNextItem |
340 | \since QtQuick.Controls 2.1 (Qt 5.8) |
341 | \readonly |
342 | |
343 | This attached property is \c true if this child is the next item. |
344 | |
345 | It is attached to each child item of the SwipeView. |
346 | */ |
347 | |
348 | /*! |
349 | \qmlattachedproperty bool QtQuick.Controls::SwipeView::isPreviousItem |
350 | \since QtQuick.Controls 2.1 (Qt 5.8) |
351 | \readonly |
352 | |
353 | This attached property is \c true if this child is the previous item. |
354 | |
355 | It is attached to each child item of the SwipeView. |
356 | */ |
357 | |
358 | /*! |
359 | \qmlattachedproperty SwipeView QtQuick.Controls::SwipeView::view |
360 | \readonly |
361 | |
362 | This attached property holds the view that manages this child item. |
363 | |
364 | It is attached to each child item of the SwipeView. |
365 | */ |
366 | |
367 | void QQuickSwipeViewAttachedPrivate::updateCurrentIndex() |
368 | { |
369 | setCurrentIndex(swipeView ? swipeView->currentIndex() : -1); |
370 | } |
371 | |
372 | void QQuickSwipeViewAttachedPrivate::setCurrentIndex(int i) |
373 | { |
374 | if (i == currentIndex) |
375 | return; |
376 | |
377 | Q_Q(QQuickSwipeViewAttached); |
378 | const bool wasCurrent = q->isCurrentItem(); |
379 | const bool wasNext = q->isNextItem(); |
380 | const bool wasPrevious = q->isPreviousItem(); |
381 | |
382 | currentIndex = i; |
383 | if (wasCurrent != q->isCurrentItem()) |
384 | emit q->isCurrentItemChanged(); |
385 | if (wasNext != q->isNextItem()) |
386 | emit q->isNextItemChanged(); |
387 | if (wasPrevious != q->isPreviousItem()) |
388 | emit q->isPreviousItemChanged(); |
389 | } |
390 | |
391 | void QQuickSwipeViewAttachedPrivate::update(QQuickSwipeView *newView, int newIndex) |
392 | { |
393 | Q_Q(QQuickSwipeViewAttached); |
394 | int oldIndex = index; |
395 | QQuickSwipeView *oldView = swipeView; |
396 | |
397 | index = newIndex; |
398 | swipeView = newView; |
399 | |
400 | if (oldView != newView) { |
401 | if (oldView) { |
402 | disconnect(sender: oldView, signal: &QQuickSwipeView::currentIndexChanged, |
403 | receiverPrivate: this, slot: &QQuickSwipeViewAttachedPrivate::updateCurrentIndex); |
404 | } |
405 | if (newView) { |
406 | connect(sender: newView, signal: &QQuickSwipeView::currentIndexChanged, |
407 | receiverPrivate: this, slot: &QQuickSwipeViewAttachedPrivate::updateCurrentIndex); |
408 | } |
409 | emit q->viewChanged(); |
410 | } |
411 | if (oldIndex != newIndex) |
412 | emit q->indexChanged(); |
413 | |
414 | updateCurrentIndex(); |
415 | } |
416 | |
417 | QQuickSwipeViewAttached::QQuickSwipeViewAttached(QObject *parent) |
418 | : QObject(*(new QQuickSwipeViewAttachedPrivate), parent) |
419 | { |
420 | if (!qobject_cast<QQuickItem *>(o: parent)) |
421 | qmlWarning(me: parent) << "SwipeView: attached properties must be accessed from within a child item" ; |
422 | } |
423 | |
424 | int QQuickSwipeViewAttached::index() const |
425 | { |
426 | Q_D(const QQuickSwipeViewAttached); |
427 | return d->index; |
428 | } |
429 | |
430 | bool QQuickSwipeViewAttached::isCurrentItem() const |
431 | { |
432 | Q_D(const QQuickSwipeViewAttached); |
433 | return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex; |
434 | } |
435 | |
436 | QQuickSwipeView *QQuickSwipeViewAttached::view() const |
437 | { |
438 | Q_D(const QQuickSwipeViewAttached); |
439 | return d->swipeView; |
440 | } |
441 | |
442 | bool QQuickSwipeViewAttached::isNextItem() const |
443 | { |
444 | Q_D(const QQuickSwipeViewAttached); |
445 | return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex + 1; |
446 | } |
447 | |
448 | bool QQuickSwipeViewAttached::isPreviousItem() const |
449 | { |
450 | Q_D(const QQuickSwipeViewAttached); |
451 | return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex - 1; |
452 | } |
453 | |
454 | QT_END_NAMESPACE |
455 | |
456 | #include "moc_qquickswipeview_p.cpp" |
457 | |