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