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: |
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 | |
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(index: 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(name: "_q_QQuickSwipeView_warned" ).toBool()) { |
155 | qmlWarning(me: item) << "SwipeView has detected conflicting anchors. Unable to layout the item." ; |
156 | item->setProperty(name: "_q_QQuickSwipeView_warned" , value: 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 | |
168 | QQuickSwipeViewPrivate *QQuickSwipeViewPrivate::get(QQuickSwipeView *view) |
169 | { |
170 | return view->d_func(); |
171 | } |
172 | |
173 | void QQuickSwipeViewPrivate::itemImplicitWidthChanged(QQuickItem *item) |
174 | { |
175 | Q_Q(QQuickSwipeView); |
176 | QQuickContainerPrivate::itemImplicitWidthChanged(item); |
177 | if (item == q->currentItem()) |
178 | updateImplicitContentWidth(); |
179 | } |
180 | |
181 | void QQuickSwipeViewPrivate::itemImplicitHeightChanged(QQuickItem *item) |
182 | { |
183 | Q_Q(QQuickSwipeView); |
184 | QQuickContainerPrivate::itemImplicitHeightChanged(item); |
185 | if (item == q->currentItem()) |
186 | updateImplicitContentHeight(); |
187 | } |
188 | |
189 | qreal QQuickSwipeViewPrivate::getContentWidth() const |
190 | { |
191 | Q_Q(const QQuickSwipeView); |
192 | QQuickItem *currentItem = q->currentItem(); |
193 | return currentItem ? currentItem->implicitWidth() : 0; |
194 | } |
195 | |
196 | qreal QQuickSwipeViewPrivate::getContentHeight() const |
197 | { |
198 | Q_Q(const QQuickSwipeView); |
199 | QQuickItem *currentItem = q->currentItem(); |
200 | return currentItem ? currentItem->implicitHeight() : 0; |
201 | } |
202 | |
203 | QQuickSwipeView::QQuickSwipeView(QQuickItem *parent) |
204 | : QQuickContainer(*(new QQuickSwipeViewPrivate), parent) |
205 | { |
206 | Q_D(QQuickSwipeView); |
207 | d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; |
208 | setFlag(flag: ItemIsFocusScope); |
209 | setActiveFocusOnTab(true); |
210 | QObjectPrivate::connect(sender: this, signal: &QQuickContainer::currentItemChanged, receiverPrivate: d, slot: &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 | */ |
222 | bool QQuickSwipeView::isInteractive() const |
223 | { |
224 | Q_D(const QQuickSwipeView); |
225 | return d->interactive; |
226 | } |
227 | |
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 | */ |
250 | Qt::Orientation QQuickSwipeView::orientation() const |
251 | { |
252 | Q_D(const QQuickSwipeView); |
253 | return d->orientation; |
254 | } |
255 | |
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 | */ |
277 | 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 | */ |
292 | bool QQuickSwipeView::isVertical() const |
293 | { |
294 | Q_D(const QQuickSwipeView); |
295 | return d->orientation == Qt::Vertical; |
296 | } |
297 | |
298 | QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object) |
299 | { |
300 | return new QQuickSwipeViewAttached(object); |
301 | } |
302 | |
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 | |
310 | void QQuickSwipeView::itemAdded(int index, QQuickItem *item) |
311 | { |
312 | Q_D(QQuickSwipeView); |
313 | if (isComponentComplete()) |
314 | item->setSize(QSizeF(d->contentItem->width(), d->contentItem->height())); |
315 | QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(object: qmlAttachedPropertiesObject<QQuickSwipeView>(obj: item)); |
316 | if (attached) |
317 | QQuickSwipeViewAttachedPrivate::get(attached)->update(newView: this, newIndex: index); |
318 | } |
319 | |
320 | void QQuickSwipeView::itemMoved(int index, QQuickItem *item) |
321 | { |
322 | QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(object: qmlAttachedPropertiesObject<QQuickSwipeView>(obj: item)); |
323 | if (attached) |
324 | QQuickSwipeViewAttachedPrivate::get(attached)->update(newView: this, newIndex: index); |
325 | } |
326 | |
327 | void QQuickSwipeView::itemRemoved(int, QQuickItem *item) |
328 | { |
329 | QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(object: qmlAttachedPropertiesObject<QQuickSwipeView>(obj: item)); |
330 | if (attached) |
331 | QQuickSwipeViewAttachedPrivate::get(attached)->update(newView: nullptr, newIndex: -1); |
332 | } |
333 | |
334 | #if QT_CONFIG(accessibility) |
335 | QAccessible::Role QQuickSwipeView::accessibleRole() const |
336 | { |
337 | return QAccessible::PageTabList; |
338 | } |
339 | #endif |
340 | |
341 | /*! |
342 | \qmlattachedproperty int QtQuick.Controls::SwipeView::index |
343 | \readonly |
344 | |
345 | This attached property holds the index of each child item in the SwipeView. |
346 | |
347 | It is attached to each child item of the SwipeView. |
348 | */ |
349 | |
350 | /*! |
351 | \qmlattachedproperty bool QtQuick.Controls::SwipeView::isCurrentItem |
352 | \readonly |
353 | |
354 | This attached property is \c true if this child is the current item. |
355 | |
356 | It is attached to each child item of the SwipeView. |
357 | */ |
358 | |
359 | /*! |
360 | \qmlattachedproperty bool QtQuick.Controls::SwipeView::isNextItem |
361 | \since QtQuick.Controls 2.1 (Qt 5.8) |
362 | \readonly |
363 | |
364 | This attached property is \c true if this child is the next item. |
365 | |
366 | It is attached to each child item of the SwipeView. |
367 | */ |
368 | |
369 | /*! |
370 | \qmlattachedproperty bool QtQuick.Controls::SwipeView::isPreviousItem |
371 | \since QtQuick.Controls 2.1 (Qt 5.8) |
372 | \readonly |
373 | |
374 | This attached property is \c true if this child is the previous item. |
375 | |
376 | It is attached to each child item of the SwipeView. |
377 | */ |
378 | |
379 | /*! |
380 | \qmlattachedproperty SwipeView QtQuick.Controls::SwipeView::view |
381 | \readonly |
382 | |
383 | This attached property holds the view that manages this child item. |
384 | |
385 | It is attached to each child item of the SwipeView. |
386 | */ |
387 | |
388 | void QQuickSwipeViewAttachedPrivate::updateCurrentIndex() |
389 | { |
390 | setCurrentIndex(swipeView ? swipeView->currentIndex() : -1); |
391 | } |
392 | |
393 | void QQuickSwipeViewAttachedPrivate::setCurrentIndex(int i) |
394 | { |
395 | if (i == currentIndex) |
396 | return; |
397 | |
398 | Q_Q(QQuickSwipeViewAttached); |
399 | const bool wasCurrent = q->isCurrentItem(); |
400 | const bool wasNext = q->isNextItem(); |
401 | const bool wasPrevious = q->isPreviousItem(); |
402 | |
403 | currentIndex = i; |
404 | if (wasCurrent != q->isCurrentItem()) |
405 | emit q->isCurrentItemChanged(); |
406 | if (wasNext != q->isNextItem()) |
407 | emit q->isNextItemChanged(); |
408 | if (wasPrevious != q->isPreviousItem()) |
409 | emit q->isPreviousItemChanged(); |
410 | } |
411 | |
412 | void QQuickSwipeViewAttachedPrivate::update(QQuickSwipeView *newView, int newIndex) |
413 | { |
414 | Q_Q(QQuickSwipeViewAttached); |
415 | int oldIndex = index; |
416 | QQuickSwipeView *oldView = swipeView; |
417 | |
418 | index = newIndex; |
419 | swipeView = newView; |
420 | |
421 | if (oldView != newView) { |
422 | if (oldView) { |
423 | disconnect(sender: oldView, signal: &QQuickSwipeView::currentIndexChanged, |
424 | receiverPrivate: this, slot: &QQuickSwipeViewAttachedPrivate::updateCurrentIndex); |
425 | } |
426 | if (newView) { |
427 | connect(sender: newView, signal: &QQuickSwipeView::currentIndexChanged, |
428 | receiverPrivate: this, slot: &QQuickSwipeViewAttachedPrivate::updateCurrentIndex); |
429 | } |
430 | emit q->viewChanged(); |
431 | } |
432 | if (oldIndex != newIndex) |
433 | emit q->indexChanged(); |
434 | |
435 | updateCurrentIndex(); |
436 | } |
437 | |
438 | QQuickSwipeViewAttached::QQuickSwipeViewAttached(QObject *parent) |
439 | : QObject(*(new QQuickSwipeViewAttachedPrivate), parent) |
440 | { |
441 | if (!qobject_cast<QQuickItem *>(object: parent)) |
442 | qmlWarning(me: parent) << "SwipeView: attached properties must be accessed from within a child item" ; |
443 | } |
444 | |
445 | int QQuickSwipeViewAttached::index() const |
446 | { |
447 | Q_D(const QQuickSwipeViewAttached); |
448 | return d->index; |
449 | } |
450 | |
451 | bool QQuickSwipeViewAttached::isCurrentItem() const |
452 | { |
453 | Q_D(const QQuickSwipeViewAttached); |
454 | return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex; |
455 | } |
456 | |
457 | QQuickSwipeView *QQuickSwipeViewAttached::view() const |
458 | { |
459 | Q_D(const QQuickSwipeViewAttached); |
460 | return d->swipeView; |
461 | } |
462 | |
463 | bool QQuickSwipeViewAttached::isNextItem() const |
464 | { |
465 | Q_D(const QQuickSwipeViewAttached); |
466 | return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex + 1; |
467 | } |
468 | |
469 | bool QQuickSwipeViewAttached::isPreviousItem() const |
470 | { |
471 | Q_D(const QQuickSwipeViewAttached); |
472 | return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex - 1; |
473 | } |
474 | |
475 | QT_END_NAMESPACE |
476 | |