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
9QT_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
75class QQuickSwipeViewPrivate : public QQuickContainerPrivate
76{
77 Q_DECLARE_PUBLIC(QQuickSwipeView)
78
79public:
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
95class QQuickSwipeViewAttachedPrivate : public QObjectPrivate
96{
97public:
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
114void 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
129void 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
140QQuickSwipeViewPrivate *QQuickSwipeViewPrivate::get(QQuickSwipeView *view)
141{
142 return view->d_func();
143}
144
145void QQuickSwipeViewPrivate::itemImplicitWidthChanged(QQuickItem *item)
146{
147 Q_Q(QQuickSwipeView);
148 QQuickContainerPrivate::itemImplicitWidthChanged(item);
149 if (item == q->currentItem())
150 updateImplicitContentWidth();
151}
152
153void QQuickSwipeViewPrivate::itemImplicitHeightChanged(QQuickItem *item)
154{
155 Q_Q(QQuickSwipeView);
156 QQuickContainerPrivate::itemImplicitHeightChanged(item);
157 if (item == q->currentItem())
158 updateImplicitContentHeight();
159}
160
161qreal QQuickSwipeViewPrivate::getContentWidth() const
162{
163 Q_Q(const QQuickSwipeView);
164 QQuickItem *currentItem = q->currentItem();
165 return currentItem ? currentItem->implicitWidth() : 0;
166}
167
168qreal QQuickSwipeViewPrivate::getContentHeight() const
169{
170 Q_Q(const QQuickSwipeView);
171 QQuickItem *currentItem = q->currentItem();
172 return currentItem ? currentItem->implicitHeight() : 0;
173}
174
175QQuickSwipeView::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*/
194bool QQuickSwipeView::isInteractive() const
195{
196 Q_D(const QQuickSwipeView);
197 return d->interactive;
198}
199
200void 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*/
222Qt::Orientation QQuickSwipeView::orientation() const
223{
224 Q_D(const QQuickSwipeView);
225 return d->orientation;
226}
227
228void 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*/
249bool 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*/
264bool QQuickSwipeView::isVertical() const
265{
266 Q_D(const QQuickSwipeView);
267 return d->orientation == Qt::Vertical;
268}
269
270QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object)
271{
272 return new QQuickSwipeViewAttached(object);
273}
274
275void QQuickSwipeView::componentComplete()
276{
277 Q_D(QQuickSwipeView);
278 QQuickContainer::componentComplete();
279 d->resizeItems();
280}
281
282void QQuickSwipeView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
283{
284 Q_D(QQuickSwipeView);
285 QQuickContainer::geometryChange(newGeometry, oldGeometry);
286 d->resizeItems();
287}
288
289void 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
299void 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
306void 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)
314QAccessible::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
367void QQuickSwipeViewAttachedPrivate::updateCurrentIndex()
368{
369 setCurrentIndex(swipeView ? swipeView->currentIndex() : -1);
370}
371
372void 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
391void 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
417QQuickSwipeViewAttached::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
424int QQuickSwipeViewAttached::index() const
425{
426 Q_D(const QQuickSwipeViewAttached);
427 return d->index;
428}
429
430bool QQuickSwipeViewAttached::isCurrentItem() const
431{
432 Q_D(const QQuickSwipeViewAttached);
433 return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex;
434}
435
436QQuickSwipeView *QQuickSwipeViewAttached::view() const
437{
438 Q_D(const QQuickSwipeViewAttached);
439 return d->swipeView;
440}
441
442bool QQuickSwipeViewAttached::isNextItem() const
443{
444 Q_D(const QQuickSwipeViewAttached);
445 return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex + 1;
446}
447
448bool QQuickSwipeViewAttached::isPreviousItem() const
449{
450 Q_D(const QQuickSwipeViewAttached);
451 return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex - 1;
452}
453
454QT_END_NAMESPACE
455
456#include "moc_qquickswipeview_p.cpp"
457

source code of qtdeclarative/src/quicktemplates/qquickswipeview.cpp