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

source code of qtdeclarative/src/quick/items/qquickpathview.cpp