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