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