Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickpathview.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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// Qt-Security score:significant reason:default
4
7#include "qquickflickablebehavior_p.h" //Contains flicking behavior defines
8#include "qquicktext_p.h"
9
10#include <QtQml/qqmlcomponent.h>
11#include <QtQuick/private/qquickpointerhandler_p.h>
12#include <QtQuick/private/qquickstate_p.h>
13#include <private/qqmlglobal_p.h>
14#include <private/qqmlopenmetaobject_p.h>
15#include <private/qqmlchangeset_p.h>
16#include <qpa/qplatformtheme.h>
17
18#include <QtQml/qqmlinfo.h>
19
20#include <QtGui/private/qeventpoint_p.h>
21#include <QtGui/qevent.h>
22#include <QtGui/qguiapplication.h>
23#include <QtGui/private/qguiapplication_p.h>
24#include <QtGui/qstylehints.h>
25#include <QtCore/qmath.h>
26
27#include <cmath>
28
29#if QT_CONFIG(quick_itemview)
30#include <private/qquickitemview_p.h>
31#endif
32
34
35#if !QT_CONFIG(quick_itemview)
36Q_STATIC_LOGGING_CATEGORY(lcItemViewDelegateLifecycle, "qt.quick.itemview.lifecycle")
37#endif
38Q_STATIC_LOGGING_CATEGORY(lcPathView, "qt.quick.pathview")
39
40static QQmlOpenMetaObjectType *qPathViewAttachedType = nullptr;
41
42QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
43: QObject(parent), m_percent(-1), m_view(nullptr), m_onPath(false), m_isCurrent(false)
44{
46 m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
47 m_metaobject->setCached(true);
48 } else {
49 m_metaobject = new QQmlOpenMetaObject(this);
50 }
51}
52
56
57QVariant QQuickPathViewAttached::value(const QByteArray &name) const
58{
59 return m_metaobject->value(name);
60}
61void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
62{
63 m_metaobject->setValue(name, val);
64}
65
66QQuickPathViewPrivate::QQuickPathViewPrivate()
67 : path(nullptr), currentIndex(0), currentItemOffset(0), startPc(0)
68 , offset(0), offsetAdj(0), mappedRange(1), mappedCache(0)
69 , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
70 , autoHighlight(true), highlightUp(false), layoutScheduled(false)
71 , moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false)
72 , inRefill(false)
73 , dragMargin(0), deceleration(100)
74 , maximumFlickVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal())
75 , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
76 , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedCacheSize(0), requestedZ(0)
77 , moveReason(Other), movementDirection(QQuickPathView::Shortest), moveDirection(QQuickPathView::Shortest)
78 , attType(nullptr), highlightComponent(nullptr), highlightItem(nullptr)
79 , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
80 , highlightPosition(0)
81 , highlightRangeStart(0), highlightRangeEnd(0)
82 , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
83 , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
84{
85 setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
86}
87
89{
90 Q_Q(QQuickPathView);
91 offset = 0;
92 q->setAcceptedMouseButtons(Qt::LeftButton);
93 q->setFlag(QQuickItem::ItemIsFocusScope);
94 q->setFiltersChildMouseEvents(true);
95 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()),
96 q, QQuickPathView, SLOT(ticked()));
97 timer.invalidate();
98 qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()),
99 q, QQuickPathView, SLOT(movementEnding()));
100}
101
102QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool async)
103{
104 Q_Q(QQuickPathView);
105 requestedIndex = modelIndex;
106 requestedZ = z;
107 inRequest = true;
108 QObject *object = model->object(modelIndex, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
109 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
110 if (!item) {
111 if (object) {
112 model->release(object);
113 if (!delegateValidated) {
114 delegateValidated = true;
115 QObject* delegate = q->delegate();
116 qmlWarning(delegate ? delegate : q) << QQuickPathView::tr("Delegate must be of Item type");
117 }
118 }
119 } else {
120 item->setParentItem(q);
121 requestedIndex = -1;
122 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
123 itemPrivate->addItemChangeListener(
124 this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed);
125 }
126 inRequest = false;
127 return item;
128}
129
130void QQuickPathView::createdItem(int index, QObject *object)
131{
132 Q_D(QQuickPathView);
133 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
134 if (d->requestedIndex != index) {
135 qPathViewAttachedType = d->attachedType();
136 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
137 qPathViewAttachedType = nullptr;
138 if (att) {
139 att->m_view = this;
140 att->setOnPath(false);
141 }
142 item->setParentItem(this);
143 d->updateItem(item, 1);
144 } else {
145 d->requestedIndex = -1;
146 if (!d->inRequest)
147 refill();
148 }
149}
150
151void QQuickPathView::initItem(int index, QObject *object)
152{
153 Q_D(QQuickPathView);
154 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
155 if (item && d->requestedIndex == index) {
156 QQuickItemPrivate::get(item)->setCulled(true);
157 item->setParentItem(this);
158 qPathViewAttachedType = d->attachedType();
159 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
160 qPathViewAttachedType = nullptr;
161 if (att) {
162 att->m_view = this;
163 qreal percent = d->positionOfIndex(index);
164 if (percent < 1 && d->path) {
165 const auto attributes = d->path->attributes();
166 for (const QString &attr : attributes)
167 att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
168 item->setZ(d->requestedZ);
169 }
170 att->setOnPath(percent < 1);
171 }
172 }
173}
174
175void QQuickPathViewPrivate::releaseItem(QQuickItem *item)
176{
177 if (!item)
178 return;
179 qCDebug(lcItemViewDelegateLifecycle) << "release" << item;
180 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
181 itemPrivate->removeItemChangeListener(
182 this, QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed);
183 if (!model)
184 return;
185 QQmlInstanceModel::ReleaseFlags flags = model->release(item);
186 if (!flags) {
187 // item was not destroyed, and we no longer reference it.
188 if (QQuickPathViewAttached *att = attached(item))
189 att->setOnPath(false);
190 } else if (flags & QQmlInstanceModel::Destroyed) {
191 // but we still reference it
192 item->setParentItem(nullptr);
193 }
194}
195
197{
198 return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
199}
200
201QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
202{
203 if (!attType) {
204 // pre-create one metatype to share with all attached objects
205 attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject);
206 if (path) {
207 const auto attributes = path->attributes();
208 for (const QString &attr : attributes)
209 attType->createProperty(attr.toUtf8());
210 }
211 }
212
213 return attType;
214}
215
217{
219
220 for (QQuickItem *p : std::as_const(items))
221 releaseItem(p);
222
223 for (QQuickItem *p : std::as_const(itemCache))
224 releaseItem(p);
225
226 if (requestedIndex >= 0) {
227 if (model)
228 model->cancel(requestedIndex);
229 requestedIndex = -1;
230 }
231
232 items.clear();
233 itemCache.clear();
234 tl.clear();
235}
236
238{
239 // Update the actual cache size to be at max
240 // the available non-visible items.
242
243 if (model && pathItems != -1 && pathItems < modelCount) {
244 mappedRange = qreal(modelCount)/pathItems;
245 mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
246 } else {
247 mappedRange = 1;
248 mappedCache = 0;
249 }
250}
251
253{
254 qreal pos = -1;
255
256 if (model && index >= 0 && index < modelCount) {
257 qreal start = 0;
258 if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
259 || snapMode != QQuickPathView::NoSnap))
260 start = highlightRangeStart;
261 qreal globalPos = index + offset;
262 globalPos = std::fmod(globalPos, qreal(modelCount)) / modelCount;
263 if (pathItems != -1 && pathItems < modelCount) {
264 globalPos += start / mappedRange;
265 globalPos = std::fmod(globalPos, qreal(1));
266 pos = globalPos * mappedRange;
267 } else {
268 pos = std::fmod(globalPos + start, qreal(1));
269 }
270 }
271
272 return pos;
273}
274
275// returns true if position is between lower and upper, taking into
276// account the circular space.
277bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower,
278 qreal upper, bool emptyRangeCheck) const
279{
280 if (emptyRangeCheck && qFuzzyCompare(lower, upper))
281 return true;
282 if (lower > upper) {
283 if (position > upper && position > lower)
284 position -= mappedRange;
285 lower -= mappedRange;
286 }
287 return position >= lower && position < upper;
288}
289
291{
292 Q_Q(QQuickPathView);
293 if (!q->isComponentComplete())
294 return;
295
296 bool changed = false;
297 if (highlightItem) {
298 highlightItem->setParentItem(nullptr);
299 highlightItem->deleteLater();
300 highlightItem = nullptr;
301 changed = true;
302 }
303
304 QQuickItem *item = nullptr;
305 if (highlightComponent) {
306 QQmlContext *creationContext = highlightComponent->creationContext();
307 QQmlContext *highlightContext = new QQmlContext(
308 creationContext ? creationContext : qmlContext(q));
309 QObject *nobj = highlightComponent->create(highlightContext);
310 if (nobj) {
311 QQml_setParent_noEvent(highlightContext, nobj);
312 item = qobject_cast<QQuickItem *>(nobj);
313 if (!item)
314 delete nobj;
315 } else {
316 delete highlightContext;
317 }
318 } else {
319 item = new QQuickItem;
320 }
321 if (item) {
322 QQml_setParent_noEvent(item, q);
323 item->setParentItem(q);
324 highlightItem = item;
325 changed = true;
326 }
327 if (changed)
328 emit q->highlightItemChanged();
329}
330
332{
333 Q_Q(QQuickPathView);
334 if (!q->isComponentComplete() || !isValid())
335 return;
336 if (highlightItem) {
337 if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
338 updateItem(highlightItem, highlightRangeStart);
339 } else {
340 qreal target = currentIndex;
341
342 offsetAdj = 0;
343 tl.reset(moveHighlight);
344 moveHighlight.setValue(highlightPosition);
345
346 const int duration = highlightMoveDuration;
347
348 if (target - highlightPosition > modelCount/2) {
349 highlightUp = false;
350 qreal distance = modelCount - target + highlightPosition;
351 tl.move(moveHighlight, 0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
352 tl.set(moveHighlight, modelCount-0.01);
353 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
354 } else if (target - highlightPosition <= -modelCount/2) {
355 highlightUp = true;
356 qreal distance = modelCount - highlightPosition + target;
357 tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
358 tl.set(moveHighlight, 0);
359 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
360 } else {
361 highlightUp = highlightPosition - target < 0;
362 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
363 }
364 }
365 }
366}
367
369{
370 if (!(qFuzzyCompare(pos, highlightPosition))) {
371 qreal start = 0;
372 qreal end = 1;
373 if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) {
374 start = highlightRangeStart;
375 end = highlightRangeEnd;
376 }
377
378 qreal range = qreal(modelCount);
379 // calc normalized position of highlight relative to offset
380 qreal relativeHighlight = std::fmod(pos + offset, range) / range;
381
382 if (!highlightUp && relativeHighlight > end / mappedRange) {
383 qreal diff = 1 - relativeHighlight;
384 setOffset(offset + diff * range);
385 } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) {
386 qreal diff = relativeHighlight - (end - start) / mappedRange;
387 setOffset(offset - diff * range - 0.00001);
388 }
389
390 highlightPosition = pos;
391 qreal pathPos = positionOfIndex(pos);
392 updateItem(highlightItem, pathPos);
394 att->setOnPath(pathPos < 1);
395 }
396}
397
398void QQuickPathView::pathUpdated()
399{
400 Q_D(QQuickPathView);
401 for (QQuickItem *item : std::as_const(d->items)) {
402 if (QQuickPathViewAttached *att = d->attached(item))
403 att->m_percent = -1;
404 }
405 refill();
406}
407
408void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent)
409{
410 if (!path)
411 return;
412 if (QQuickPathViewAttached *att = attached(item)) {
413 if (qFuzzyCompare(att->m_percent, percent))
414 return;
415 att->m_percent = percent;
416 const auto attributes = path->attributes();
417 for (const QString &attr : attributes)
418 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
419 att->setOnPath(percent < 1);
420 }
421 QQuickItemPrivate::get(item)->setCulled(percent >= 1);
422 QPointF pf = path->pointAtPercent(qMin(percent, qreal(1)));
423 item->setX(pf.x() - item->width()/2);
424 item->setY(pf.y() - item->height()/2);
425}
426
428{
429 Q_Q(QQuickPathView);
430 if (!q->isComponentComplete())
431 return;
432
433 clear();
434
435 if (!isValid())
436 return;
437
439 q->refill();
440}
441
443{
444 Q_Q(QQuickPathView);
445 if (dragging == d)
446 return;
447
448 dragging = d;
449 if (dragging)
450 emit q->dragStarted();
451 else
452 emit q->dragEnded();
453
454 emit q->draggingChanged();
455}
456
457/*!
458 \qmltype PathView
459 \nativetype QQuickPathView
460 \inqmlmodule QtQuick
461 \ingroup qtquick-paths
462 \ingroup qtquick-views
463 \inherits Item
464 \brief Lays out model-provided items on a path.
465
466 A PathView displays data from models created from built-in QML types like ListModel
467 and XmlListModel, or custom model classes defined in C++ that inherit from
468 QAbstractListModel.
469
470 The view has a \l model, which defines the data to be displayed, and
471 a \l delegate, which defines how the data should be displayed.
472 The \l delegate is instantiated for each item on the \l path.
473 The items may be flicked to move them along the path.
474
475 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
476
477 \snippet qml/pathview/ContactModel.qml 0
478
479 This data can be represented as a PathView, like this:
480
481 \snippet qml/pathview/pathview.qml 0
482
483 \image pathview.gif
484
485 (Note the above example uses PathAttribute to scale and modify the
486 opacity of the items as they rotate. This additional code can be seen in the
487 PathAttribute documentation.)
488
489 PathView does not automatically handle keyboard navigation. This is because
490 the keys to use for navigation will depend upon the shape of the path. Navigation
491 can be added quite simply by setting \c focus to \c true and calling
492 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
493 using the left and right arrow keys:
494
495 \qml
496 PathView {
497 // ...
498 focus: true
499 Keys.onLeftPressed: decrementCurrentIndex()
500 Keys.onRightPressed: incrementCurrentIndex()
501 }
502 \endqml
503
504 The path view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
505
506 Delegates are instantiated as needed and may be destroyed at any time.
507 State should \e never be stored in a delegate.
508
509 PathView attaches a number of properties to the root item of the delegate, for example
510 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
511 this attached property directly as \c PathView.isCurrentItem, while the child
512 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
513
514 \snippet qml/pathview/pathview.qml 1
515
516 \b Note that views do not enable \e clip automatically. If the view
517 is not clipped by another item or the screen, it will be necessary
518 to set \e {clip: true} in order to have the out of view items clipped
519 nicely.
520
521 \sa Path, {QML Data Models}, ListView, GridView, {Qt Quick Examples - Views}
522*/
523
524QQuickPathView::QQuickPathView(QQuickItem *parent)
525 : QQuickItem(*(new QQuickPathViewPrivate), parent)
526{
527 Q_D(QQuickPathView);
528 d->init();
529}
530
531QQuickPathView::~QQuickPathView()
532{
533 Q_D(QQuickPathView);
534 d->clear();
535 if (d->attType)
536 d->attType->release();
537 if (d->ownModel)
538 delete d->model;
539}
540
541/*!
542 \qmlattachedproperty PathView QtQuick::PathView::view
543 \readonly
544
545 This attached property holds the view that manages this delegate instance.
546
547 It is attached to each instance of the delegate.
548*/
549
550/*!
551 \qmlattachedproperty bool QtQuick::PathView::onPath
552 \readonly
553
554 This attached property holds whether the item is currently on the path.
555
556 If a pathItemCount has been set, it is possible that some items may
557 be instantiated, but not considered to be currently on the path.
558 Usually, these items would be set invisible, for example:
559
560 \qml
561 Component {
562 Rectangle {
563 visible: PathView.onPath
564 // ...
565 }
566 }
567 \endqml
568
569 It is attached to each instance of the delegate.
570*/
571
572/*!
573 \qmlattachedproperty bool QtQuick::PathView::isCurrentItem
574 \readonly
575
576 This attached property is true if this delegate is the current item; otherwise false.
577
578 It is attached to each instance of the delegate.
579
580 This property may be used to adjust the appearance of the current item.
581
582 \snippet qml/pathview/pathview.qml 1
583*/
584
585/*!
586 \qmlproperty model QtQuick::PathView::model
587 This property holds the model providing data for the view.
588
589 The model provides a set of data that is used to create the items for the view.
590 For large or dynamic datasets the model is usually provided by a C++ model object.
591 Models can also be created directly in QML, using the ListModel type.
592
593 \note changing the model will reset the offset and currentIndex to 0.
594
595 \sa {qml-data-models}{Data Models}
596*/
597QVariant QQuickPathView::model() const
598{
599 Q_D(const QQuickPathView);
600 return d->modelVariant;
601}
602
603void QQuickPathView::setModel(const QVariant &m)
604{
605 Q_D(QQuickPathView);
606 QVariant model = m;
607 if (model.userType() == qMetaTypeId<QJSValue>())
608 model = model.value<QJSValue>().toVariant();
609
610 if (d->modelVariant == model)
611 return;
612
613 if (d->model) {
614 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
615 this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
616 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
617 this, QQuickPathView, SLOT(createdItem(int,QObject*)));
618 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
619 this, QQuickPathView, SLOT(initItem(int,QObject*)));
620 d->clear();
621 }
622
623 d->modelVariant = model;
624 QObject *object = qvariant_cast<QObject*>(model);
625 QQmlInstanceModel *vim = nullptr;
626 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
627 if (d->ownModel) {
628 delete d->model;
629 d->ownModel = false;
630 }
631 d->model = vim;
632 } else {
633 if (!d->ownModel) {
634 d->model = new QQmlDelegateModel(qmlContext(this));
635 d->ownModel = true;
636 if (isComponentComplete())
637 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
638 }
639 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
640 dataModel->setModel(model);
641 }
642 int oldModelCount = d->modelCount;
643 d->modelCount = 0;
644 if (d->model) {
645 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
646 this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
647 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
648 this, QQuickPathView, SLOT(createdItem(int,QObject*)));
649 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
650 this, QQuickPathView, SLOT(initItem(int,QObject*)));
651 d->modelCount = d->model->count();
652 }
653 if (isComponentComplete()) {
654 if (d->currentIndex != 0) {
655 d->currentIndex = 0;
656 emit currentIndexChanged();
657 }
658 if (!(qFuzzyIsNull(d->offset))) {
659 d->offset = 0;
660 emit offsetChanged();
661 }
662 }
663 d->regenerate();
664 if (d->modelCount != oldModelCount)
665 emit countChanged();
666 emit modelChanged();
667}
668
669/*!
670 \qmlproperty int QtQuick::PathView::count
671 This property holds the number of items in the model.
672*/
673int QQuickPathView::count() const
674{
675 Q_D(const QQuickPathView);
676 return d->model ? d->modelCount : 0;
677}
678
679/*!
680 \qmlproperty Path QtQuick::PathView::path
681 This property holds the path used to lay out the items.
682 For more information see the \l Path documentation.
683*/
684QQuickPath *QQuickPathView::path() const
685{
686 Q_D(const QQuickPathView);
687 return d->path;
688}
689
690void QQuickPathView::setPath(QQuickPath *path)
691{
692 Q_D(QQuickPathView);
693 if (d->path == path)
694 return;
695 if (d->path)
696 qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
697 this, QQuickPathView, SLOT(pathUpdated()));
698 d->path = path;
699
700 if (path) {
701 qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
702 this, QQuickPathView, SLOT(pathUpdated()));
703 }
704
705 if (isComponentComplete()) {
706 d->clear();
707 if (d->isValid()) {
708 if (d->attType) {
709 d->attType->release();
710 d->attType = nullptr;
711 }
712 d->regenerate();
713 }
714 }
715
716 emit pathChanged();
717}
718
719/*!
720 \qmlproperty int QtQuick::PathView::currentIndex
721 This property holds the index of the current item.
722*/
723int QQuickPathView::currentIndex() const
724{
725 Q_D(const QQuickPathView);
726 return d->currentIndex;
727}
728
729void QQuickPathView::setCurrentIndex(int idx)
730{
731 Q_D(QQuickPathView);
732 if (!isComponentComplete()) {
733 if (idx != d->currentIndex) {
734 d->currentIndex = idx;
735 emit currentIndexChanged();
736 }
737 return;
738 }
739
740 idx = d->modelCount
741 ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
742 : 0;
743 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
744 const bool hadCurrentItem = d->currentItem != nullptr;
745 const int oldCurrentIdx = d->currentIndex;
746 if (hadCurrentItem) {
747 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
748 att->setIsCurrentItem(false);
749 d->releaseCurrentItem();
750 }
751 d->moveReason = QQuickPathViewPrivate::SetIndex;
752 d->currentIndex = idx;
753 if (d->modelCount) {
754 d->createCurrentItem();
755 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
756 d->snapToIndex(d->currentIndex, QQuickPathViewPrivate::SetIndex);
757 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
758 d->updateHighlight();
759 }
760 if (oldCurrentIdx != d->currentIndex)
761 emit currentIndexChanged();
762 if (hadCurrentItem)
763 emit currentItemChanged();
764 }
765}
766
767/*!
768 \qmlproperty Item QtQuick::PathView::currentItem
769 This property holds the current item in the view.
770*/
771QQuickItem *QQuickPathView::currentItem() const
772{
773 Q_D(const QQuickPathView);
774 return d->currentItem;
775}
776
777/*!
778 \qmlmethod void QtQuick::PathView::incrementCurrentIndex()
779
780 Increments the current index.
781
782 \b Note: methods should only be called after the Component has completed.
783*/
784void QQuickPathView::incrementCurrentIndex()
785{
786 Q_D(QQuickPathView);
787 d->moveDirection = QQuickPathView::Positive;
788 setCurrentIndex(currentIndex()+1);
789}
790
791/*!
792 \qmlmethod void QtQuick::PathView::decrementCurrentIndex()
793
794 Decrements the current index.
795
796 \b Note: methods should only be called after the Component has completed.
797*/
798void QQuickPathView::decrementCurrentIndex()
799{
800 Q_D(QQuickPathView);
801 d->moveDirection = QQuickPathView::Negative;
802 setCurrentIndex(currentIndex()-1);
803}
804
805/*!
806 \qmlproperty real QtQuick::PathView::offset
807
808 The offset specifies how far along the path the items are from their initial positions.
809 This is a real number that ranges from \c 0 to the count of items in the model.
810*/
811qreal QQuickPathView::offset() const
812{
813 Q_D(const QQuickPathView);
814 return d->offset;
815}
816
817void QQuickPathView::setOffset(qreal offset)
818{
819 Q_D(QQuickPathView);
820 d->moveReason = QQuickPathViewPrivate::Other;
821 d->setOffset(offset);
822 d->updateCurrent();
823}
824
826{
827 Q_Q(QQuickPathView);
828 if (!qFuzzyCompare(offset, o)) {
829 if (isValid() && q->isComponentComplete()) {
830 qreal oldOffset = offset;
831 offset = std::fmod(o, qreal(modelCount));
832 if (offset < 0)
833 offset += qreal(modelCount);
834 qCDebug(lcItemViewDelegateLifecycle) << o << "was" << oldOffset << "now" << offset;
835 q->refill();
836 } else {
837 offset = o;
838 }
839 emit q->offsetChanged();
840 }
841}
842
844{
845 setOffset(o+offsetAdj);
846}
847
848/*!
849 \qmlproperty Component QtQuick::PathView::highlight
850 This property holds the component to use as the highlight.
851
852 An instance of the highlight component will be created for each view.
853 The geometry of the resultant component instance will be managed by the view
854 so as to stay with the current item.
855
856 The below example demonstrates how to make a simple highlight. Note the use
857 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
858 the highlight is hidden when flicked away from the path.
859
860 \qml
861 Component {
862 Rectangle {
863 visible: PathView.onPath
864 // ...
865 }
866 }
867 \endqml
868
869 \sa highlightItem, highlightRangeMode
870*/
871QQmlComponent *QQuickPathView::highlight() const
872{
873 Q_D(const QQuickPathView);
874 return d->highlightComponent;
875}
876
877void QQuickPathView::setHighlight(QQmlComponent *highlight)
878{
879 Q_D(QQuickPathView);
880 if (highlight != d->highlightComponent) {
881 d->highlightComponent = highlight;
882 d->createHighlight();
883 d->updateHighlight();
884 emit highlightChanged();
885 }
886}
887
888/*!
889 \qmlproperty Item QtQuick::PathView::highlightItem
890
891 \c highlightItem holds the highlight item, which was created
892 from the \l highlight component.
893
894 \sa highlight
895*/
896QQuickItem *QQuickPathView::highlightItem() const
897{
898 Q_D(const QQuickPathView);
899 return d->highlightItem;
900}
901
902/*!
903 \qmlproperty real QtQuick::PathView::preferredHighlightBegin
904 \qmlproperty real QtQuick::PathView::preferredHighlightEnd
905 \qmlproperty enumeration QtQuick::PathView::highlightRangeMode
906
907 These properties set the preferred range of the highlight (current item)
908 within the view. The preferred values must be in the range from \c 0 to \c 1.
909
910 Valid values for \c highlightRangeMode are:
911
912 \value PathView.NoHighlightRange no range is applied: the highlight
913 will move freely within the view.
914 \value PathView.ApplyRange the view will attempt to maintain the highlight
915 within the range, however the highlight can move
916 outside of the range at the ends of the path or
917 due to a mouse interaction.
918 \value PathView.StrictlyEnforceRange the highlight will never move outside of the range.
919 This means that the current item will change if a
920 keyboard or mouse action would cause the highlight
921 to move outside of the range.
922
923 The default value is \e PathView.StrictlyEnforceRange.
924
925 Defining a highlight range is the correct way to influence where the
926 current item ends up when the view moves. For example, if you want the
927 currently selected item to be in the middle of the path, then set the
928 highlight range to be 0.5,0.5 and highlightRangeMode to \e PathView.StrictlyEnforceRange.
929 Then, when the path scrolls,
930 the currently selected item will be the item at that position. This also applies to
931 when the currently selected item changes - it will scroll to within the preferred
932 highlight range. Furthermore, the behaviour of the current item index will occur
933 whether or not a highlight exists.
934
935 \note A valid range requires \c preferredHighlightEnd to be greater
936 than or equal to \c preferredHighlightBegin.
937*/
938qreal QQuickPathView::preferredHighlightBegin() const
939{
940 Q_D(const QQuickPathView);
941 return d->highlightRangeStart;
942}
943
944void QQuickPathView::setPreferredHighlightBegin(qreal start)
945{
946 Q_D(QQuickPathView);
947 if (qFuzzyCompare(d->highlightRangeStart, start) || start < 0 || start > 1)
948 return;
949 d->highlightRangeStart = start;
950 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
951 refill();
952 emit preferredHighlightBeginChanged();
953}
954
955qreal QQuickPathView::preferredHighlightEnd() const
956{
957 Q_D(const QQuickPathView);
958 return d->highlightRangeEnd;
959}
960
961void QQuickPathView::setPreferredHighlightEnd(qreal end)
962{
963 Q_D(QQuickPathView);
964 if (qFuzzyCompare(d->highlightRangeEnd, end) || end < 0 || end > 1)
965 return;
966 d->highlightRangeEnd = end;
967 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
968 refill();
969 emit preferredHighlightEndChanged();
970}
971
972QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const
973{
974 Q_D(const QQuickPathView);
975 return d->highlightRangeMode;
976}
977
978void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode)
979{
980 Q_D(QQuickPathView);
981 if (d->highlightRangeMode == mode)
982 return;
983 d->highlightRangeMode = mode;
984 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
985 if (d->haveHighlightRange) {
986 d->regenerate();
987 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
988 if (index >= 0)
989 d->snapToIndex(index, QQuickPathViewPrivate::Other);
990 }
991 emit highlightRangeModeChanged();
992}
993
994/*!
995 \qmlproperty int QtQuick::PathView::highlightMoveDuration
996 This property holds the move animation duration of the highlight delegate.
997
998 If the highlightRangeMode is StrictlyEnforceRange then this property
999 determines the speed that the items move along the path.
1000
1001 The default value for the duration is 300ms.
1002*/
1003int QQuickPathView::highlightMoveDuration() const
1004{
1005 Q_D(const QQuickPathView);
1006 return d->highlightMoveDuration;
1007}
1008
1009void QQuickPathView::setHighlightMoveDuration(int duration)
1010{
1011 Q_D(QQuickPathView);
1012 if (d->highlightMoveDuration == duration)
1013 return;
1014 d->highlightMoveDuration = duration;
1015 emit highlightMoveDurationChanged();
1016}
1017
1018/*!
1019 \qmlproperty real QtQuick::PathView::dragMargin
1020 This property holds the maximum distance from the path that initiates mouse dragging.
1021
1022 By default the path can only be dragged by clicking on an item. If
1023 dragMargin is greater than zero, a drag can be initiated by clicking
1024 within dragMargin pixels of the path.
1025*/
1026qreal QQuickPathView::dragMargin() const
1027{
1028 Q_D(const QQuickPathView);
1029 return d->dragMargin;
1030}
1031
1032void QQuickPathView::setDragMargin(qreal dragMargin)
1033{
1034 Q_D(QQuickPathView);
1035 if (qFuzzyCompare(d->dragMargin, dragMargin))
1036 return;
1037 d->dragMargin = dragMargin;
1038 emit dragMarginChanged();
1039}
1040
1041/*!
1042 \qmlproperty real QtQuick::PathView::flickDeceleration
1043 This property holds the rate at which a flick will decelerate.
1044
1045 The default is 100.
1046*/
1047qreal QQuickPathView::flickDeceleration() const
1048{
1049 Q_D(const QQuickPathView);
1050 return d->deceleration;
1051}
1052
1053void QQuickPathView::setFlickDeceleration(qreal dec)
1054{
1055 Q_D(QQuickPathView);
1056 if (qFuzzyCompare(d->deceleration, dec))
1057 return;
1058 d->deceleration = dec;
1059 emit flickDecelerationChanged();
1060}
1061
1062/*!
1063 \qmlproperty real QtQuick::PathView::maximumFlickVelocity
1064 This property holds the approximate maximum velocity that the user can flick the view in pixels/second.
1065
1066 The default value is platform dependent.
1067*/
1068qreal QQuickPathView::maximumFlickVelocity() const
1069{
1070 Q_D(const QQuickPathView);
1071 return d->maximumFlickVelocity;
1072}
1073
1074void QQuickPathView::setMaximumFlickVelocity(qreal vel)
1075{
1076 Q_D(QQuickPathView);
1077 if (qFuzzyCompare(vel, d->maximumFlickVelocity))
1078 return;
1079 d->maximumFlickVelocity = vel;
1080 emit maximumFlickVelocityChanged();
1081}
1082
1083
1084/*!
1085 \qmlproperty bool QtQuick::PathView::interactive
1086
1087 A user cannot drag or flick a PathView that is not interactive.
1088
1089 This property is useful for temporarily disabling flicking. This allows
1090 special interaction with PathView's children.
1091*/
1092bool QQuickPathView::isInteractive() const
1093{
1094 Q_D(const QQuickPathView);
1095 return d->interactive;
1096}
1097
1098void QQuickPathView::setInteractive(bool interactive)
1099{
1100 Q_D(QQuickPathView);
1101 if (interactive != d->interactive) {
1102 d->interactive = interactive;
1103 if (!interactive)
1104 d->tl.clear();
1105 emit interactiveChanged();
1106 }
1107}
1108
1109/*!
1110 \qmlproperty bool QtQuick::PathView::moving
1111
1112 This property holds whether the view is currently moving
1113 due to the user either dragging or flicking the view.
1114*/
1115bool QQuickPathView::isMoving() const
1116{
1117 Q_D(const QQuickPathView);
1118 return d->moving;
1119}
1120
1121/*!
1122 \qmlproperty bool QtQuick::PathView::flicking
1123
1124 This property holds whether the view is currently moving
1125 due to the user flicking the view.
1126*/
1127bool QQuickPathView::isFlicking() const
1128{
1129 Q_D(const QQuickPathView);
1130 return d->flicking;
1131}
1132
1133/*!
1134 \qmlproperty bool QtQuick::PathView::dragging
1135
1136 This property holds whether the view is currently moving
1137 due to the user dragging the view.
1138*/
1139bool QQuickPathView::isDragging() const
1140{
1141 Q_D(const QQuickPathView);
1142 return d->dragging;
1143}
1144
1145/*!
1146 \qmlsignal QtQuick::PathView::movementStarted()
1147
1148 This signal is emitted when the view begins moving due to user
1149 interaction.
1150*/
1151
1152/*!
1153 \qmlsignal QtQuick::PathView::movementEnded()
1154
1155 This signal is emitted when the view stops moving due to user
1156 interaction. If a flick was generated, this signal will
1157 be emitted once the flick stops. If a flick was not
1158 generated, this signal will be emitted when the
1159 user stops dragging - i.e. a mouse or touch release.
1160*/
1161
1162/*!
1163 \qmlsignal QtQuick::PathView::flickStarted()
1164
1165 This signal is emitted when the view is flicked. A flick
1166 starts from the point that the mouse or touch is released,
1167 while still in motion.
1168*/
1169
1170/*!
1171 \qmlsignal QtQuick::PathView::flickEnded()
1172
1173 This signal is emitted when the view stops moving due to a flick.
1174*/
1175
1176/*!
1177 \qmlsignal QtQuick::PathView::dragStarted()
1178
1179 This signal is emitted when the view starts to be dragged due to user
1180 interaction.
1181*/
1182
1183/*!
1184 \qmlsignal QtQuick::PathView::dragEnded()
1185
1186 This signal is emitted when the user stops dragging the view.
1187
1188 If the velocity of the drag is suffient at the time the
1189 touch/mouse button is released then a flick will start.
1190*/
1191
1192/*!
1193 \qmlproperty Component QtQuick::PathView::delegate
1194
1195 The delegate provides a template defining each item instantiated by the view.
1196 The index is exposed as an accessible \c index property. Properties of the
1197 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
1198
1199 The number of objects and bindings in the delegate has a direct effect on the
1200 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
1201 that is not needed for the normal display of the delegate in a \l Loader which
1202 can load additional components when needed.
1203
1204 Note that the PathView will layout the items based on the size of the root
1205 item in the delegate.
1206
1207 Here is an example delegate:
1208 \snippet qml/pathview/pathview.qml 1
1209*/
1210QQmlComponent *QQuickPathView::delegate() const
1211{
1212 Q_D(const QQuickPathView);
1213 if (d->model) {
1214 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
1215 return dataModel->delegate();
1216 }
1217
1218 return nullptr;
1219}
1220
1221void QQuickPathView::setDelegate(QQmlComponent *delegate)
1222{
1223 Q_D(QQuickPathView);
1224 if (delegate == this->delegate())
1225 return;
1226 if (!d->ownModel) {
1227 d->model = new QQmlDelegateModel(qmlContext(this));
1228 d->ownModel = true;
1229 if (isComponentComplete())
1230 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1231 }
1232 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) {
1233 int oldCount = dataModel->count();
1234 dataModel->setDelegate(delegate);
1235 d->modelCount = dataModel->count();
1236 d->regenerate();
1237 if (oldCount != dataModel->count())
1238 emit countChanged();
1239 emit delegateChanged();
1240 d->delegateValidated = false;
1241 }
1242}
1243
1244/*!
1245 \qmlproperty int QtQuick::PathView::pathItemCount
1246 This property holds the number of items visible on the path at any one time.
1247
1248 Setting pathItemCount to undefined will show all items on the path.
1249*/
1250int QQuickPathView::pathItemCount() const
1251{
1252 Q_D(const QQuickPathView);
1253 return d->pathItems;
1254}
1255
1256void QQuickPathView::setPathItemCount(int i)
1257{
1258 Q_D(QQuickPathView);
1259 if (i == d->pathItems)
1260 return;
1261 if (i < 1)
1262 i = 1;
1263 d->pathItems = i;
1264 d->updateMappedRange();
1265 if (d->isValid() && isComponentComplete()) {
1266 d->regenerate();
1267 }
1268 emit pathItemCountChanged();
1269}
1270
1271void QQuickPathView::resetPathItemCount()
1272{
1273 Q_D(QQuickPathView);
1274 if (-1 == d->pathItems)
1275 return;
1276 d->pathItems = -1;
1277 d->updateMappedRange();
1278 if (d->isValid() && isComponentComplete())
1279 d->regenerate();
1280 emit pathItemCountChanged();
1281}
1282
1283/*!
1284 \qmlproperty int QtQuick::PathView::cacheItemCount
1285 This property holds the maximum number of items to cache off the path.
1286
1287 For example, a PathView with a model containing 20 items, a pathItemCount
1288 of 10, and an cacheItemCount of 4 will create up to 14 items, with 10 visible
1289 on the path and 4 invisible cached items.
1290
1291 The cached delegates are created asynchronously,
1292 allowing creation to occur across multiple frames and reducing the
1293 likelihood of skipping frames.
1294
1295 \note Setting this property is not a replacement for creating efficient delegates.
1296 It can improve the smoothness of scrolling behavior at the expense of additional
1297 memory usage. The fewer objects and bindings in a delegate, the faster a
1298 view can be scrolled. It is important to realize that setting cacheItemCount
1299 will only postpone issues caused by slow-loading delegates, it is not a
1300 solution for this scenario.
1301
1302 \sa pathItemCount
1303*/
1304int QQuickPathView::cacheItemCount() const
1305{
1306 Q_D(const QQuickPathView);
1307 return d->requestedCacheSize;
1308}
1309
1310void QQuickPathView::setCacheItemCount(int i)
1311{
1312 Q_D(QQuickPathView);
1313 if (i == d->requestedCacheSize || i < 0)
1314 return;
1315
1316 d->requestedCacheSize = i;
1317 d->updateMappedRange();
1318 refill();
1319 emit cacheItemCountChanged();
1320}
1321
1322/*!
1323 \qmlproperty enumeration QtQuick::PathView::snapMode
1324
1325 This property determines how the items will settle following a drag or flick.
1326 The possible values are:
1327
1328 \value PathView.NoSnap (default) the items stop anywhere along the path.
1329 \value PathView.SnapToItem the items settle with an item aligned with the \l preferredHighlightBegin.
1330 \value PathView.SnapOneItem the items settle no more than one item away from the item nearest
1331 \l preferredHighlightBegin at the time the press is released. This mode is particularly
1332 useful for moving one page at a time.
1333
1334 \c snapMode does not affect the \l currentIndex. To update the
1335 \l currentIndex as the view is moved, set \l highlightRangeMode
1336 to \c PathView.StrictlyEnforceRange (default for PathView).
1337
1338 \sa highlightRangeMode
1339*/
1340QQuickPathView::SnapMode QQuickPathView::snapMode() const
1341{
1342 Q_D(const QQuickPathView);
1343 return d->snapMode;
1344}
1345
1346void QQuickPathView::setSnapMode(SnapMode mode)
1347{
1348 Q_D(QQuickPathView);
1349 if (mode == d->snapMode)
1350 return;
1351 d->snapMode = mode;
1352 emit snapModeChanged();
1353}
1354
1355/*!
1356 \qmlproperty enumeration QtQuick::PathView::movementDirection
1357 \since 5.7
1358
1359 This property determines the direction in which items move when setting the current index.
1360 The possible values are:
1361
1362 \value PathView.Shortest (default) the items move in the direction that requires the least
1363 movement, which could be either \c Negative or \c Positive.
1364 \value PathView.Negative the items move backwards towards their destination.
1365 \value PathView.Positive the items move forwards towards their destination.
1366
1367 For example, suppose that there are 5 items in the model, and \l currentIndex is \c 0.
1368 If currentIndex is set to \c 2,
1369
1370 \list
1371 \li a \c Positive movement direction will result in the following order: 0, 1, 2
1372 \li a \c Negative movement direction will result in the following order: 0, 5, 4, 3, 2
1373 \li a \c Shortest movement direction will result in same order with \c Positive .
1374 \endlist
1375
1376 \note this property doesn't affect the movement of \l incrementCurrentIndex() and \l decrementCurrentIndex().
1377*/
1378QQuickPathView::MovementDirection QQuickPathView::movementDirection() const
1379{
1380 Q_D(const QQuickPathView);
1381 return d->movementDirection;
1382}
1383
1384void QQuickPathView::setMovementDirection(QQuickPathView::MovementDirection dir)
1385{
1386 Q_D(QQuickPathView);
1387 if (dir == d->movementDirection)
1388 return;
1389 d->movementDirection = dir;
1390 if (!d->tl.isActive())
1391 d->moveDirection = d->movementDirection;
1392 emit movementDirectionChanged();
1393}
1394
1395/*!
1396 \qmlmethod void QtQuick::PathView::positionViewAtIndex(int index, PositionMode mode)
1397
1398 Positions the view such that the \a index is at the position specified by
1399 \a mode:
1400
1401 \value PathView.Beginning position item at the beginning of the path.
1402 \value PathView.Center position item in the center of the path.
1403 \value PathView.End position item at the end of the path.
1404 \value PathView.Contain ensure the item is positioned on the path.
1405 \value PathView.SnapPosition position the item at \l preferredHighlightBegin. This mode
1406 is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled
1407 via \l snapMode.
1408
1409 \b Note: methods should only be called after the Component has completed. To position
1410 the view at startup, this method should be called by Component.onCompleted. For
1411 example, to position the view at the end:
1412
1413 \code
1414 Component.onCompleted: positionViewAtIndex(count - 1, PathView.End)
1415 \endcode
1416*/
1417void QQuickPathView::positionViewAtIndex(int index, int mode)
1418{
1419 Q_D(QQuickPathView);
1420 if (!d->isValid())
1421 return;
1422 if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView
1423 return;
1424
1425 if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems))
1426 return;
1427
1428 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1429 int idx = (index+d->modelCount) % d->modelCount;
1430 bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1431 || d->snapMode != QQuickPathView::NoSnap);
1432
1433 qreal beginOffset;
1434 qreal endOffset;
1435 if (snap) {
1436 beginOffset = d->modelCount - idx - qFloor(count * d->highlightRangeStart);
1437 endOffset = beginOffset + count - 1;
1438 } else {
1439 beginOffset = d->modelCount - idx;
1440 // Small offset since the last point coincides with the first and
1441 // this the only "end" position that gives the expected visual result.
1442 qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001;
1443 endOffset = std::fmod(beginOffset + count, qreal(d->modelCount)) - adj;
1444 }
1445 qreal offset = d->offset;
1446 switch (mode) {
1447 case Beginning:
1448 offset = beginOffset;
1449 break;
1450 case End:
1451 offset = endOffset;
1452 break;
1453 case Center:
1454 if (beginOffset < endOffset)
1455 offset = (beginOffset + endOffset)/2;
1456 else
1457 offset = (beginOffset + (endOffset + d->modelCount))/2;
1458 if (snap)
1459 offset = qRound(offset);
1460 break;
1461 case Contain:
1462 if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset))
1463 || (d->offset < beginOffset && d->offset > endOffset)) {
1464 qreal diff1 = std::fmod(beginOffset - d->offset + d->modelCount, qreal(d->modelCount));
1465 qreal diff2 = std::fmod(d->offset - endOffset + d->modelCount, qreal(d->modelCount));
1466 if (diff1 < diff2)
1467 offset = beginOffset;
1468 else
1469 offset = endOffset;
1470 }
1471 break;
1472 case SnapPosition:
1473 offset = d->modelCount - idx;
1474 break;
1475 }
1476
1477 d->tl.clear();
1478 setOffset(offset);
1479}
1480
1481/*!
1482 \qmlmethod int QtQuick::PathView::indexAt(real x, real y)
1483
1484 Returns the index of the item containing the point \a x, \a y in content
1485 coordinates. If there is no item at the point specified, -1 is returned.
1486
1487 \b Note: methods should only be called after the Component has completed.
1488*/
1489int QQuickPathView::indexAt(qreal x, qreal y) const
1490{
1491 Q_D(const QQuickPathView);
1492 QQuickItem *item = itemAt(x, y);
1493 return item ? d->model->indexOf(item, nullptr) : -1;
1494}
1495
1496/*!
1497 \qmlmethod Item QtQuick::PathView::itemAt(real x, real y)
1498
1499 Returns the item containing the point \a x, \a y in content
1500 coordinates. If there is no item at the point specified, null is returned.
1501
1502 \b Note: methods should only be called after the Component has completed.
1503*/
1504QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const
1505{
1506 Q_D(const QQuickPathView);
1507 if (!d->isValid())
1508 return nullptr;
1509
1510 for (QQuickItem *item : d->items) {
1511 QPointF p = item->mapFromItem(this, QPointF(x, y));
1512 if (item->contains(p))
1513 return item;
1514 }
1515
1516 return nullptr;
1517}
1518
1519/*!
1520 \qmlmethod Item QtQuick::PathView::itemAtIndex(int index)
1521
1522 Returns the item for \a index. If there is no item for that index, for example
1523 because it has not been created yet, or because it has been panned out of
1524 the visible area and removed from the cache, null is returned.
1525
1526 \b Note: this method should only be called after the Component has completed.
1527 The returned value should also not be stored since it can turn to null
1528 as soon as control goes out of the calling scope, if the view releases that item.
1529
1530 \since 5.13
1531*/
1532QQuickItem *QQuickPathView::itemAtIndex(int index) const
1533{
1534 Q_D(const QQuickPathView);
1535 if (!d->isValid())
1536 return nullptr;
1537
1538 for (QQuickItem *item : d->items) {
1539 if (index == d->model->indexOf(item, nullptr))
1540 return item;
1541 }
1542
1543 return nullptr;
1544}
1545
1546/*!
1547 \internal
1548
1549 Returns a point in the path, that has the closest distance from \a point.
1550 A value in the range 0-1 will be written to \a nearPercent if given, which
1551 represents where on the path the \a point is closest to. \c 0 means the very
1552 beginning of the path, and \c 1 means the very end.
1553*/
1554QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1555{
1556 const auto pathLength = path->path().length();
1557 qreal samples = qMin(pathLength / 5, qreal(500));
1558 qreal res = pathLength / samples;
1559
1560 qreal mindist = 1e10; // big number
1561 QPointF nearPoint = path->pointAtPercent(0);
1562 qreal nearPc = 0;
1563
1564 // get rough pos
1565 for (qreal i=1; i < samples; i++) {
1566 QPointF pt = path->pointAtPercent(i/samples);
1567 QPointF diff = pt - point;
1568 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1569 if (dist < mindist) {
1570 nearPoint = pt;
1571 nearPc = i;
1572 mindist = dist;
1573 }
1574 }
1575
1576 // now refine
1577 qreal approxPc = nearPc;
1578 for (qreal i = approxPc-1; i < approxPc+1; i += 1/(2*res)) {
1579 QPointF pt = path->pointAtPercent(i/samples);
1580 QPointF diff = pt - point;
1581 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1582 if (dist < mindist) {
1583 nearPoint = pt;
1584 nearPc = i;
1585 mindist = dist;
1586 }
1587 }
1588
1589 if (nearPercent)
1590 *nearPercent = nearPc / samples;
1591
1592 return nearPoint;
1593}
1594
1596{
1597 velocityBuffer.append(v);
1598 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
1599 velocityBuffer.remove(0);
1600 qCDebug(lcPathView) << "instantaneous velocity" << v;
1601}
1602
1604{
1605 qreal velocity = 0;
1606 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
1607 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
1608 for (int i = 0; i < count; ++i) {
1609 qreal v = velocityBuffer.at(i);
1610 velocity += v;
1611 }
1612 velocity /= count;
1613 qCDebug(lcPathView) << "average velocity" << velocity << "based on" << count << "samples";
1614 }
1615 return velocity;
1616}
1617
1619{
1620 if (0 != event->timestamp())
1621 return qint64(event->timestamp());
1622 return timer.elapsed();
1623}
1624
1625void QQuickPathView::mousePressEvent(QMouseEvent *event)
1626{
1627 Q_D(QQuickPathView);
1628 if (d->interactive) {
1629 d->handleMousePressEvent(event);
1630 event->accept();
1631 } else {
1632 QQuickItem::mousePressEvent(event);
1633 }
1634}
1635
1637{
1638 Q_Q(QQuickPathView);
1639 if (!interactive || !items.size() || !model || !modelCount)
1640 return;
1641 velocityBuffer.clear();
1642 int idx = 0;
1643 for (; idx < items.size(); ++idx) {
1644 QQuickItem *item = items.at(idx);
1645 if (item->contains(item->mapFromScene(event->scenePosition())))
1646 break;
1647 }
1648 if (idx == items.size() && qFuzzyIsNull(dragMargin)) // didn't click on an item
1649 return;
1650
1651 startPoint = pointNear(event->position(), &startPc);
1652 startPos = event->position();
1653 if (idx == items.size()) {
1654 qreal distance = qAbs(event->position().x() - startPoint.x()) + qAbs(event->position().y() - startPoint.y());
1655 if (distance > dragMargin)
1656 return;
1657 }
1658
1659 if (tl.isActive() && flicking && flickDuration && qreal(tl.time()) / flickDuration < 0.8) {
1660 stealMouse = true; // If we've been flicked then steal the click.
1661 q->grabMouse(); // grab it right now too, just to be sure (QTBUG-77173)
1662 } else {
1663 stealMouse = false;
1664 }
1665 q->setKeepMouseGrab(stealMouse);
1666
1667 timer.start();
1668 lastPosTime = computeCurrentTime(event);
1669 tl.clear();
1670}
1671
1672void QQuickPathView::mouseMoveEvent(QMouseEvent *event)
1673{
1674 Q_D(QQuickPathView);
1675 if (d->interactive) {
1676 d->handleMouseMoveEvent(event);
1677 event->accept();
1678 } else {
1679 QQuickItem::mouseMoveEvent(event);
1680 }
1681}
1682
1684{
1685 Q_Q(QQuickPathView);
1686 if (!interactive || !timer.isValid() || !model || !modelCount)
1687 return;
1688
1689 qint64 currentTimestamp = computeCurrentTime(event);
1690 qreal newPc;
1691 QPointF pathPoint = pointNear(event->position(), &newPc);
1692 if (!stealMouse) {
1693 QPointF posDelta = event->position() - startPos;
1694 if (QQuickDeliveryAgentPrivate::dragOverThreshold(posDelta.y(), Qt::YAxis, event) ||
1695 QQuickDeliveryAgentPrivate::dragOverThreshold(posDelta.x(), Qt::XAxis, event)) {
1696 // The touch has exceeded the threshold. If the movement along the path is close to the drag threshold
1697 // then we'll assume that this gesture targets the PathView. This ensures PathView gesture grabbing
1698 // is in sync with other items.
1699 QPointF pathDelta = pathPoint - startPoint;
1700 const int startDragDistance = QGuiApplication::styleHints()->startDragDistance();
1701 if (qAbs(pathDelta.x()) > startDragDistance * 0.8
1702 || qAbs(pathDelta.y()) > startDragDistance * 0.8) {
1703 stealMouse = true;
1704 q->setKeepMouseGrab(true);
1705 }
1706 }
1707 } else {
1709 int count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
1710 qreal diff = (newPc - startPc)*count;
1711 if (!qFuzzyIsNull(diff)) {
1712 q->setOffset(offset + diff);
1713
1714 if (diff > modelCount/2)
1715 diff -= modelCount;
1716 else if (diff < -modelCount/2)
1717 diff += modelCount;
1718
1719 qint64 elapsed = currentTimestamp - lastPosTime;
1720 if (elapsed > 0)
1721 addVelocitySample(diff / (qreal(elapsed) / 1000));
1722 }
1723 if (!moving) {
1724 moving = true;
1725 emit q->movingChanged();
1726 emit q->movementStarted();
1727 }
1728 setDragging(true);
1729 }
1730 startPc = newPc;
1731 lastPosTime = currentTimestamp;
1732}
1733
1734void QQuickPathView::mouseReleaseEvent(QMouseEvent *event)
1735{
1736 Q_D(QQuickPathView);
1737 if (d->interactive) {
1738 d->handleMouseReleaseEvent(event);
1739 event->accept();
1740 ungrabMouse();
1741 } else {
1742 QQuickItem::mouseReleaseEvent(event);
1743 }
1744}
1745
1747{
1748 Q_Q(QQuickPathView);
1749 stealMouse = false;
1750 q->setKeepMouseGrab(false);
1751 setDragging(false);
1752 if (!interactive || !timer.isValid() || !model || !modelCount) {
1753 timer.invalidate();
1754 if (!tl.isActive())
1755 q->movementEnding();
1756 return;
1757 }
1758
1759 qreal velocity = calcVelocity();
1760 qint64 elapsed = computeCurrentTime(event) - lastPosTime;
1761 // Let the velocity linearly decay such that it becomes 0 if elapsed time > QML_FLICK_VELOCITY_DECAY_TIME
1762 // The intention is that if you are flicking at some speed, then stop in one place for some time before releasing,
1763 // the previous velocity is lost. (QTBUG-77173, QTBUG-59052)
1764 velocity *= qreal(qMax(0LL, QML_FLICK_VELOCITY_DECAY_TIME - elapsed)) / QML_FLICK_VELOCITY_DECAY_TIME;
1765 qCDebug(lcPathView) << "after elapsed time" << elapsed << "velocity decayed to" << velocity;
1766 qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
1767 const auto averageItemLength = path->path().length() / count;
1768 qreal pixelVelocity = averageItemLength * velocity;
1769 if (qAbs(pixelVelocity) > _q_MinimumFlickVelocity) {
1770 if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
1771 // limit velocity
1772 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1773 velocity = maxVel / averageItemLength;
1774 }
1775 // Calculate the distance to be travelled
1776 qreal v2 = velocity*velocity;
1777 qreal accel = deceleration/10;
1778 qreal dist = 0;
1779 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
1780 || snapMode != QQuickPathView::NoSnap)) {
1781 if (snapMode == QQuickPathView::SnapOneItem) {
1782 // encourage snapping one item in direction of motion
1783 if (velocity > 0)
1784 dist = qRound(0.5 + offset) - offset;
1785 else
1786 dist = qRound(0.5 - offset) + offset;
1787 } else {
1788 // + 0.25 to encourage moving at least one item in the flick direction
1789 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2) + 0.25));
1790
1791 // round to nearest item.
1792 if (velocity > 0)
1793 dist = qRound(dist + offset) - offset;
1794 else
1795 dist = qRound(dist - offset) + offset;
1796 }
1797 // Calculate accel required to stop on item boundary
1798 if (dist <= 0) {
1799 dist = 0;
1800 accel = 0;
1801 } else {
1802 accel = v2 / (2 * qAbs(dist));
1803 }
1804 } else {
1805 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2)));
1806 }
1807 flickDuration = int(1000 * qAbs(velocity) / accel);
1808 offsetAdj = 0;
1809 moveOffset.setValue(offset);
1810 tl.accel(moveOffset, velocity, accel, dist);
1811 tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1812 if (!flicking) {
1813 flicking = true;
1814 emit q->flickingChanged();
1815 emit q->flickStarted();
1816 }
1817 } else {
1819 }
1820
1821 timer.invalidate();
1822 if (!tl.isActive())
1823 q->movementEnding();
1824}
1825
1826bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e)
1827{
1828 Q_D(QQuickPathView);
1829 if (!isVisible() || !d->interactive || !e->isPointerEvent())
1830 return QQuickItem::childMouseEventFilter(i, e);
1831
1832 QPointerEvent *pe = static_cast<QPointerEvent *>(e);
1833 if (QQuickDeliveryAgentPrivate::isMouseEvent(pe)) {
1834 // The event is localized for the intended receiver (in the delegate, probably),
1835 // but we need to look at position relative to the PathView itself.
1836 const auto &point = pe->points().first();
1837 QPointF localPos = mapFromScene(point.scenePosition());
1838 QObject *grabber = pe->exclusiveGrabber(point);
1839 QQuickPointerHandler *grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber);
1840 QQuickItem *grabberItem = grabberHandler ? grabberHandler->parentItem() : qmlobject_cast<QQuickItem *>(grabber);
1841 if (grabberItem == this && d->stealMouse) {
1842 // we are already the grabber and we do want the mouse event to ourselves.
1843 return true;
1844 }
1845
1846 bool grabberDisabled = (grabberItem && !grabberItem->isEnabled()) || (grabberHandler && !grabberHandler->enabled());
1847 bool stealThisEvent = d->stealMouse;
1848 const bool mouseFromTouch = pe->pointingDevice()->type() == QPointingDevice::DeviceType::TouchScreen;
1849 if ((stealThisEvent || contains(localPos)) && (!grabberItem ||
1850 (!grabberItem->keepMouseGrab() && (!mouseFromTouch || !grabberItem->keepTouchGrab())) || grabberDisabled)) {
1851 // Make a localized copy of the QMouseEvent.
1852 QMutableSinglePointEvent localizedEvent(*static_cast<QMouseEvent *>(pe));
1853 QMutableEventPoint::setPosition(localizedEvent.point(0), localPos);
1854 localizedEvent.setAccepted(false);
1855
1856 switch (localizedEvent.type()) {
1857 case QEvent::MouseMove:
1858 d->handleMouseMoveEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
1859 break;
1860 case QEvent::MouseButtonPress:
1861 d->handleMousePressEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
1862 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1863 break;
1864 case QEvent::MouseButtonRelease:
1865 d->handleMouseReleaseEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
1866 break;
1867 default:
1868 break;
1869 }
1870
1871 grabber = localizedEvent.exclusiveGrabber(localizedEvent.points().first());
1872 grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber);
1873 grabberItem = grabberHandler ? grabberHandler->parentItem() : qmlobject_cast<QQuickItem *>(grabber);
1874 grabberDisabled = (grabberItem && !grabberItem->isEnabled()) || (grabberHandler && !grabberHandler->enabled());
1875 if ((grabberItem && stealThisEvent && !grabberItem->keepMouseGrab() && grabber != this) || grabberDisabled)
1876 pe->setExclusiveGrabber(point, this);
1877
1878 const bool filtered = stealThisEvent || grabberDisabled;
1879 if (filtered)
1880 pe->setAccepted(stealThisEvent && grabberItem == this && !grabberDisabled);
1881
1882 return filtered;
1883 } else if (d->timer.isValid()) {
1884 d->timer.invalidate();
1885 d->fixOffset();
1886 }
1887 if (pe->type() == QEvent::MouseButtonRelease || (grabberItem && grabberItem->keepMouseGrab() && !grabberDisabled))
1888 d->stealMouse = false;
1889 return false;
1890 }
1891
1892 return QQuickItem::childMouseEventFilter(i, e);
1893}
1894
1895void QQuickPathView::mouseUngrabEvent()
1896{
1897 Q_D(QQuickPathView);
1898 if (d->stealMouse ||
1899 (!d->flicking && d->snapMode != NoSnap && !qFuzzyCompare(qRound(d->offset), d->offset))) {
1900 // if our mouse grab has been removed (probably by a Flickable),
1901 // or if we should snap but haven't done it, fix our state
1902 d->stealMouse = false;
1903 setKeepMouseGrab(false);
1904 d->timer.invalidate();
1905 d->fixOffset();
1906 d->setDragging(false);
1907 if (!d->tl.isActive())
1908 movementEnding();
1909 }
1910}
1911
1912void QQuickPathView::updatePolish()
1913{
1914 QQuickItem::updatePolish();
1915 refill();
1916}
1917
1918static inline int currentIndexRemainder(int currentIndex, int modelCount) noexcept
1919{
1920 if (currentIndex < 0)
1921 return modelCount + currentIndex % modelCount;
1922 else
1923 return currentIndex % modelCount;
1924}
1925
1926void QQuickPathView::componentComplete()
1927{
1928 Q_D(QQuickPathView);
1929 if (d->model && d->ownModel)
1930 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1931
1932 QQuickItem::componentComplete();
1933
1934 if (d->model) {
1935 d->modelCount = d->model->count();
1936 if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex
1937 d->offset = std::fmod(qreal(d->modelCount - currentIndexRemainder(d->currentIndex, d->modelCount)), qreal(d->modelCount));
1938 }
1939
1940 d->createHighlight();
1941 d->regenerate();
1942 d->updateHighlight();
1943 d->updateCurrent();
1944
1945 if (d->modelCount)
1946 emit countChanged();
1947}
1948
1949void QQuickPathView::refill()
1950{
1951 Q_D(QQuickPathView);
1952
1953 if (d->inRefill) {
1954 d->scheduleLayout();
1955 return;
1956 }
1957
1958 d->layoutScheduled = false;
1959
1960 if (!d->isValid() || !isComponentComplete())
1961 return;
1962
1963 d->inRefill = true;
1964
1965 bool currentVisible = false;
1966 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1967
1968 // first move existing items and remove items off path
1969 qCDebug(lcItemViewDelegateLifecycle) << "currentIndex" << d->currentIndex << "offset" << d->offset;
1970 QList<QQuickItem*>::iterator it = d->items.begin();
1971 while (it != d->items.end()) {
1972 QQuickItem *item = *it;
1973 int idx = d->model->indexOf(item, nullptr);
1974 qreal pos = d->positionOfIndex(idx);
1975 if (lcItemViewDelegateLifecycle().isDebugEnabled()) {
1976 QQuickText *text = qmlobject_cast<QQuickText*>(item);
1977 if (text)
1978 qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ": QQuickText" << text->objectName() << QStringView{text->text()}.left(40);
1979 else
1980 qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ":" << item;
1981 }
1982 if (pos < 1) {
1983 d->updateItem(item, pos);
1984 if (idx == d->currentIndex) {
1985 currentVisible = true;
1986 d->currentItemOffset = pos;
1987 }
1988 ++it;
1989 } else {
1990 d->updateItem(item, pos);
1991 if (QQuickPathViewAttached *att = d->attached(item))
1992 att->setOnPath(pos < 1);
1993 if (!d->isInBound(pos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) {
1994 qCDebug(lcItemViewDelegateLifecycle) << "release" << idx << "@" << pos << ", !isInBound: lower" << (d->mappedRange - d->mappedCache) << "upper" << (1 + d->mappedCache);
1995 d->releaseItem(item);
1996 it = d->items.erase(it);
1997 } else {
1998 ++it;
1999 }
2000 }
2001 }
2002
2003 bool waiting = false;
2004 if (d->modelCount) {
2005 // add items as needed
2006 if (d->items.size() < count+d->cacheSize) {
2007 int endIdx = 0;
2008 qreal endPos;
2009 int startIdx = 0;
2010 qreal startPos = 0;
2011 const bool wasEmpty = d->items.isEmpty();
2012 if (!wasEmpty) {
2013 //Find the beginning and end, items may not be in sorted order
2014 endPos = -1;
2015 startPos = 2;
2016
2017 for (QQuickItem * item : std::as_const(d->items)) {
2018 int idx = d->model->indexOf(item, nullptr);
2019 qreal curPos = d->positionOfIndex(idx);
2020 if (curPos > endPos) {
2021 endPos = curPos;
2022 endIdx = idx;
2023 }
2024
2025 if (curPos < startPos) {
2026 startPos = curPos;
2027 startIdx = idx;
2028 }
2029 }
2030 } else {
2031 if (d->haveHighlightRange
2032 && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
2033 || d->snapMode != QQuickPathView::NoSnap))
2034 startPos = d->highlightRangeStart;
2035 // With no items, then "end" is just off the top so we populate via append
2036 endIdx = (qRound(d->modelCount - d->offset) - 1) % d->modelCount;
2037 endIdx = qMax(-1, endIdx); // endIdx shouldn't be smaller than -1
2038 endPos = d->positionOfIndex(endIdx);
2039 }
2040 //Append
2041 int idx = endIdx + 1;
2042 if (idx >= d->modelCount)
2043 idx = 0;
2044 qreal nextPos = d->positionOfIndex(idx);
2045 while ((d->isInBound(nextPos, endPos, 1 + d->mappedCache, false) || !d->items.size())
2046 && d->items.size() < count + d->cacheSize) {
2047 qCDebug(lcItemViewDelegateLifecycle) << "append" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
2048 QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
2049 if (!item) {
2050 waiting = true;
2051 break;
2052 }
2053 if (d->items.contains(item)) {
2054 d->releaseItem(item);
2055 break; //Otherwise we'd "re-add" it, and get confused
2056 }
2057 if (d->currentIndex == idx) {
2058 currentVisible = true;
2059 d->currentItemOffset = nextPos;
2060 }
2061 d->items.append(item);
2062 d->updateItem(item, nextPos);
2063 endIdx = idx;
2064 endPos = nextPos;
2065 ++idx;
2066 if (idx >= d->modelCount)
2067 idx = 0;
2068 nextPos = d->positionOfIndex(idx);
2069 }
2070
2071 //Prepend
2072 idx = (wasEmpty ? d->calcCurrentIndex() : startIdx) - 1;
2073
2074 if (idx < 0)
2075 idx = d->modelCount - 1;
2076 nextPos = d->positionOfIndex(idx);
2077 while (!waiting && d->isInBound(nextPos, d->mappedRange - d->mappedCache, startPos)
2078 && d->items.size() < count+d->cacheSize) {
2079 qCDebug(lcItemViewDelegateLifecycle) << "prepend" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
2080 QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
2081 if (!item) {
2082 waiting = true;
2083 break;
2084 }
2085 if (d->items.contains(item)) {
2086 d->releaseItem(item);
2087 break; //Otherwise we'd "re-add" it, and get confused
2088 }
2089 if (d->currentIndex == idx) {
2090 currentVisible = true;
2091 d->currentItemOffset = nextPos;
2092 }
2093 d->items.prepend(item);
2094 d->updateItem(item, nextPos);
2095 startIdx = idx;
2096 startPos = nextPos;
2097 --idx;
2098 if (idx < 0)
2099 idx = d->modelCount - 1;
2100 nextPos = d->positionOfIndex(idx);
2101 }
2102
2103 // In rare cases, when jumping around with pathCount close to modelCount,
2104 // new items appear in the middle. This more generic addition iteration handles this
2105 // Since this is the rare case, we try append/prepend first and only do this if
2106 // there are gaps still left to fill.
2107 if (!waiting && d->items.size() < count+d->cacheSize) {
2108 qCDebug(lcItemViewDelegateLifecycle) << "Checking for pathview middle inserts, items count was" << d->items.size();
2109 idx = startIdx;
2110 QQuickItem *lastItem = d->items.at(0);
2111 while (idx != endIdx) {
2112 nextPos = d->positionOfIndex(idx);
2113 if (d->isInBound(nextPos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) {
2114 //This gets the reference from the delegate model, and will not re-create
2115 QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
2116 if (!item) {
2117 waiting = true;
2118 break;
2119 }
2120
2121 if (!d->items.contains(item)) { //We found a hole
2122 qCDebug(lcItemViewDelegateLifecycle) << "middle insert" << idx << "@" << nextPos
2123 << (d->currentIndex == idx ? "current" : "")
2124 << "items count was" << d->items.size();
2125 if (d->currentIndex == idx) {
2126 currentVisible = true;
2127 d->currentItemOffset = nextPos;
2128 }
2129 int lastListIdx = d->items.indexOf(lastItem);
2130 d->items.insert(lastListIdx + 1, item);
2131 d->updateItem(item, nextPos);
2132 } else {
2133 d->releaseItem(item);
2134 }
2135
2136 lastItem = item;
2137 }
2138
2139 ++idx;
2140 if (idx >= d->modelCount)
2141 idx = 0;
2142 }
2143 }
2144 }
2145 }
2146
2147 bool currentChanged = false;
2148 if (!currentVisible) {
2149 d->currentItemOffset = 1;
2150 if (d->currentItem) {
2151 d->updateItem(d->currentItem, 1);
2152 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
2153 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
2154 currentChanged = true;
2155 d->updateItem(d->currentItem, 1);
2156 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2157 att->setIsCurrentItem(true);
2158 }
2159 }
2160 } else if (!waiting && !d->currentItem) {
2161 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
2162 currentChanged = true;
2163 d->currentItem->setFocus(true);
2164 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2165 att->setIsCurrentItem(true);
2166 }
2167 }
2168
2169 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2170 d->updateItem(d->highlightItem, d->highlightRangeStart);
2171 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
2172 att->setOnPath(true);
2173 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
2174 d->updateItem(d->highlightItem, d->currentItemOffset);
2175 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
2176 att->setOnPath(currentVisible);
2177 }
2178 for (QQuickItem *item : std::as_const(d->itemCache))
2179 d->releaseItem(item);
2180 d->itemCache.clear();
2181
2182 d->inRefill = false;
2183 if (currentChanged)
2184 emit currentItemChanged();
2185}
2186
2187void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
2188{
2189 Q_D(QQuickPathView);
2190 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
2191 return;
2192
2193 if (reset) {
2194 d->modelCount = d->model->count();
2195 d->regenerate();
2196 emit countChanged();
2197 return;
2198 }
2199
2200 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
2201 return;
2202
2203 const int modelCount = d->modelCount;
2204 int moveId = -1;
2205 int moveOffset = 0;
2206 bool currentChanged = false;
2207 bool changedOffset = false;
2208 for (const QQmlChangeSet::Change &r : changeSet.removes()) {
2209 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
2210 d->currentIndex -= r.count;
2211 currentChanged = true;
2212 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
2213 // current item has been removed.
2214 if (r.isMove()) {
2215 moveId = r.moveId;
2216 moveOffset = d->currentIndex - r.index;
2217 } else if (d->currentItem) {
2218 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2219 att->setIsCurrentItem(true);
2220 d->releaseCurrentItem();
2221 }
2222 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
2223 currentChanged = true;
2224 }
2225
2226 if (r.index > d->currentIndex) {
2227 changedOffset = true;
2228 d->offset -= r.count;
2229 d->offsetAdj -= r.count;
2230 }
2231 d->modelCount -= r.count;
2232 }
2233 for (const QQmlChangeSet::Change &i : changeSet.inserts()) {
2234 if (d->modelCount) {
2235 if (moveId == -1 && i.index <= d->currentIndex) {
2236 d->currentIndex += i.count;
2237 currentChanged = true;
2238 } else {
2239 if (moveId != -1 && moveId == i.moveId) {
2240 d->currentIndex = i.index + moveOffset;
2241 currentChanged = true;
2242 }
2243 if (i.index > d->currentIndex) {
2244 d->offset += i.count;
2245 d->offsetAdj += i.count;
2246 changedOffset = true;
2247 }
2248 }
2249 }
2250 d->modelCount += i.count;
2251 }
2252
2253 d->offset = std::fmod(d->offset, qreal(d->modelCount));
2254 if (d->offset < 0)
2255 d->offset += d->modelCount;
2256 if (d->currentIndex == -1)
2257 d->currentIndex = d->calcCurrentIndex();
2258
2259 d->itemCache += d->items;
2260 d->items.clear();
2261
2262 if (!d->modelCount) {
2263 for (QQuickItem * item : std::as_const(d->itemCache))
2264 d->releaseItem(item);
2265 d->itemCache.clear();
2266 d->offset = 0;
2267 changedOffset = true;
2268 d->tl.reset(d->moveOffset);
2269 } else {
2270 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2271 d->offset = std::fmod(qreal(d->modelCount - d->currentIndex), qreal(d->modelCount));
2272 changedOffset = true;
2273 }
2274 d->updateMappedRange();
2275 d->scheduleLayout();
2276 }
2277 if (changedOffset)
2278 emit offsetChanged();
2279 if (currentChanged)
2280 emit currentIndexChanged();
2281 if (d->modelCount != modelCount)
2282 emit countChanged();
2283}
2284
2285void QQuickPathView::destroyingItem(QObject *item)
2286{
2287 Q_UNUSED(item);
2288}
2289
2290void QQuickPathView::ticked()
2291{
2292 Q_D(QQuickPathView);
2293 d->updateCurrent();
2294}
2295
2296void QQuickPathView::movementEnding()
2297{
2298 Q_D(QQuickPathView);
2299 if (d->flicking) {
2300 d->flicking = false;
2301 emit flickingChanged();
2302 emit flickEnded();
2303 }
2304 if (d->moving && !d->stealMouse) {
2305 d->moving = false;
2306 emit movingChanged();
2307 emit movementEnded();
2308 }
2309 d->moveDirection = d->movementDirection;
2310}
2311
2312// find the item closest to the snap position
2314{
2315 int current = 0;
2316 if (modelCount && model && items.size()) {
2317 offset = std::fmod(offset, qreal(modelCount));
2318 if (offset < 0)
2319 offset += modelCount;
2320 current = qRound(qAbs(std::fmod(modelCount - offset, qreal(modelCount))));
2321 current = current % modelCount;
2322 }
2323
2324 return current;
2325}
2326
2328{
2329 if (requestedIndex != -1)
2330 return;
2331
2332 bool inItems = false;
2333 for (QQuickItem *item : std::as_const(items)) {
2334 if (model->indexOf(item, nullptr) == currentIndex) {
2335 inItems = true;
2336 break;
2337 }
2338 }
2339
2340 if (inItems) {
2341 if ((currentItem = getItem(currentIndex, currentIndex))) {
2342 currentItem->setFocus(true);
2343 if (QQuickPathViewAttached *att = attached(currentItem))
2344 att->setIsCurrentItem(true);
2345 }
2346 } else if (currentIndex >= 0 && currentIndex < modelCount) {
2347 if ((currentItem = getItem(currentIndex, currentIndex))) {
2348 updateItem(currentItem, 1);
2349 if (QQuickPathViewAttached *att = attached(currentItem))
2350 att->setIsCurrentItem(true);
2351 }
2352 }
2353}
2354
2356{
2357 Q_Q(QQuickPathView);
2358 if (moveReason == SetIndex)
2359 return;
2360 if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
2361 return;
2362
2363 int idx = calcCurrentIndex();
2364 if (model && (idx != currentIndex || !currentItem)) {
2365 if (currentItem) {
2366 if (QQuickPathViewAttached *att = attached(currentItem))
2367 att->setIsCurrentItem(false);
2369 }
2370 int oldCurrentIndex = currentIndex;
2371 currentIndex = idx;
2372 currentItem = nullptr;
2374 if (oldCurrentIndex != currentIndex)
2375 emit q->currentIndexChanged();
2376 emit q->currentItemChanged();
2377 }
2378}
2379
2381{
2382 static_cast<QQuickPathViewPrivate *>(d)->fixOffset();
2383}
2384
2386{
2387 Q_Q(QQuickPathView);
2388 if (model && items.size()) {
2389 if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
2390 || snapMode != QQuickPathView::NoSnap)) {
2391 int curr = calcCurrentIndex();
2392 if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
2393 q->setCurrentIndex(curr);
2394 else
2396 }
2397 }
2398}
2399
2401{
2402 if (!model || modelCount <= 0)
2403 return;
2404
2405 qreal targetOffset = std::fmod(qreal(modelCount - index), qreal(modelCount));
2406 moveReason = reason;
2407 offsetAdj = 0;
2408 tl.reset(moveOffset);
2409 moveOffset.setValue(offset);
2410
2411 const int duration = highlightMoveDuration;
2412
2413 const qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount);
2414 const qreal averageItemLength = path->path().length() / count;
2415 const qreal threshold = 0.5 / averageItemLength; // if we are within .5 px, we want to immediately assign rather than animate
2416
2417 if (!duration || qAbs(offset - targetOffset) < threshold || (qFuzzyIsNull(targetOffset) && qAbs(modelCount - offset) < threshold)) {
2418 tl.set(moveOffset, targetOffset);
2419 } else if (moveDirection == QQuickPathView::Positive || (moveDirection == QQuickPathView::Shortest && targetOffset - offset > modelCount/2)) {
2420 qreal distance = modelCount - targetOffset + offset;
2421 if (targetOffset > moveOffset) {
2422 tl.move(moveOffset, 0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
2423 tl.set(moveOffset, modelCount);
2424 tl.move(moveOffset, targetOffset, QEasingCurve(qFuzzyIsNull(offset) ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
2425 } else {
2426 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2427 }
2428 } else if (moveDirection == QQuickPathView::Negative || targetOffset - offset <= -modelCount/2) {
2429 qreal distance = modelCount - offset + targetOffset;
2430 if (targetOffset < moveOffset) {
2431 tl.move(moveOffset, modelCount, QEasingCurve(qFuzzyIsNull(targetOffset) ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
2432 tl.set(moveOffset, 0);
2433 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
2434 } else {
2435 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2436 }
2437 } else {
2438 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2439 }
2440}
2441
2442QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj)
2443{
2444 return new QQuickPathViewAttached(obj);
2445}
2446
2447QT_END_NAMESPACE
2448
2449#include "moc_qquickpathview_p.cpp"
void setValue(const QByteArray &name, const QVariant &val)
QVariant value(const QByteArray &name) const
void releaseItem(QQuickItem *item)
qint64 computeCurrentTime(QInputEvent *event) const
void updateItem(QQuickItem *, qreal)
void snapToIndex(int index, MovementReason reason)
void handleMousePressEvent(QMouseEvent *event)
QQmlOpenMetaObjectType * attType
void handleMouseReleaseEvent(QMouseEvent *)
QQmlComponent * highlightComponent
void setAdjustedOffset(qreal offset)
void setHighlightPosition(qreal pos)
void handleMouseMoveEvent(QMouseEvent *event)
QPointF pointNear(const QPointF &point, qreal *nearPercent=0) const
void setOffset(qreal offset)
QQmlOpenMetaObjectType * attachedType()
qreal positionOfIndex(qreal index) const
QQuickItem * getItem(int modelIndex, qreal z=0, bool async=false)
void addVelocitySample(qreal v)
QQuickPathViewAttached * attached(QQuickItem *item)
bool isInBound(qreal position, qreal lower, qreal upper, bool emptyRangeCheck=true) const
static void fixOffsetCallback(void *)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
#define QML_FLICK_SAMPLEBUFFER
#define QML_FLICK_DISCARDSAMPLES
#define QML_FLICK_VELOCITY_DECAY_TIME
static QT_BEGIN_NAMESPACE QQmlOpenMetaObjectType * qPathViewAttachedType
static int currentIndexRemainder(int currentIndex, int modelCount) noexcept