| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickpathview_p.h" |
| #include "qquickpathview_p_p.h" |
| #include "qquickwindow.h" |
| #include "qquickflickablebehavior_p.h" //Contains flicking behavior defines |
| #include "qquicktext_p.h" |
| |
| #include <QtQuick/private/qquickstate_p.h> |
| #include <private/qqmlglobal_p.h> |
| #include <private/qqmlopenmetaobject_p.h> |
| #include <private/qqmlchangeset_p.h> |
| |
| #include <QtQml/qqmlinfo.h> |
| #include <QtGui/qevent.h> |
| #include <QtGui/qevent.h> |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/qstylehints.h> |
| #include <QtCore/qmath.h> |
| |
| #include <cmath> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(lcItemViewDelegateLifecycle) |
| Q_LOGGING_CATEGORY(lcPathView, "qt.quick.pathview") |
| |
| static const qreal MinimumFlickVelocity = 75; |
| |
| static QQmlOpenMetaObjectType *qPathViewAttachedType = nullptr; |
| |
| QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent) |
| : QObject(parent), m_percent(-1), m_view(nullptr), m_onPath(false), m_isCurrent(false) |
| { |
| if (qPathViewAttachedType) { |
| m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType); |
| m_metaobject->setCached(true); |
| } else { |
| m_metaobject = new QQmlOpenMetaObject(this); |
| } |
| } |
| |
| QQuickPathViewAttached::~QQuickPathViewAttached() |
| { |
| } |
| |
| QVariant QQuickPathViewAttached::value(const QByteArray &name) const |
| { |
| return m_metaobject->value(name); |
| } |
| void QQuickPathViewAttached::setValue(const QByteArray &name, const QVariant &val) |
| { |
| m_metaobject->setValue(name, val); |
| } |
| |
| QQuickPathViewPrivate::QQuickPathViewPrivate() |
| : path(nullptr), currentIndex(0), currentItemOffset(0), startPc(0) |
| , offset(0), offsetAdj(0), mappedRange(1), mappedCache(0) |
| , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true) |
| , autoHighlight(true), highlightUp(false), layoutScheduled(false) |
| , moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false) |
| , inRefill(false) |
| , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY) |
| , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0) |
| , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedZ(0) |
| , moveReason(Other), movementDirection(QQuickPathView::Shortest), moveDirection(QQuickPathView::Shortest) |
| , attType(nullptr), highlightComponent(nullptr), highlightItem(nullptr) |
| , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition) |
| , highlightPosition(0) |
| , highlightRangeStart(0), highlightRangeEnd(0) |
| , highlightRangeMode(QQuickPathView::StrictlyEnforceRange) |
| , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap) |
| { |
| } |
| |
| void QQuickPathViewPrivate::init() |
| { |
| Q_Q(QQuickPathView); |
| offset = 0; |
| q->setAcceptedMouseButtons(Qt::LeftButton); |
| q->setFlag(QQuickItem::ItemIsFocusScope); |
| q->setFiltersChildMouseEvents(true); |
| qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(updated()), |
| q, QQuickPathView, SLOT(ticked())); |
| timer.invalidate(); |
| qmlobject_connect(&tl, QQuickTimeLine, SIGNAL(completed()), |
| q, QQuickPathView, SLOT(movementEnding())); |
| } |
| |
| QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool async) |
| { |
| Q_Q(QQuickPathView); |
| requestedIndex = modelIndex; |
| requestedZ = z; |
| inRequest = true; |
| QObject *object = model->object(modelIndex, async ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested); |
| QQuickItem *item = qmlobject_cast<QQuickItem*>(object); |
| if (!item) { |
| if (object) { |
| model->release(object); |
| if (!delegateValidated) { |
| delegateValidated = true; |
| QObject* delegate = q->delegate(); |
| qmlWarning(delegate ? delegate : q) << QQuickPathView::tr("Delegate must be of Item type"); |
| } |
| } |
| } else { |
| item->setParentItem(q); |
| requestedIndex = -1; |
| QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
| itemPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry); |
| } |
| inRequest = false; |
| return item; |
| } |
| |
| void QQuickPathView::createdItem(int index, QObject *object) |
| { |
| Q_D(QQuickPathView); |
| QQuickItem *item = qmlobject_cast<QQuickItem*>(object); |
| if (d->requestedIndex != index) { |
| qPathViewAttachedType = d->attachedType(); |
| QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item)); |
| qPathViewAttachedType = nullptr; |
| if (att) { |
| att->m_view = this; |
| att->setOnPath(false); |
| } |
| item->setParentItem(this); |
| d->updateItem(item, 1); |
| } else { |
| d->requestedIndex = -1; |
| if (!d->inRequest) |
| refill(); |
| } |
| } |
| |
| void QQuickPathView::initItem(int index, QObject *object) |
| { |
| Q_D(QQuickPathView); |
| QQuickItem *item = qmlobject_cast<QQuickItem*>(object); |
| if (item && d->requestedIndex == index) { |
| QQuickItemPrivate::get(item)->setCulled(true); |
| item->setParentItem(this); |
| qPathViewAttachedType = d->attachedType(); |
| QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item)); |
| qPathViewAttachedType = nullptr; |
| if (att) { |
| att->m_view = this; |
| qreal percent = d->positionOfIndex(index); |
| if (percent < 1 && d->path) { |
| const auto attributes = d->path->attributes(); |
| for (const QString &attr : attributes) |
| att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent)); |
| item->setZ(d->requestedZ); |
| } |
| att->setOnPath(percent < 1); |
| } |
| } |
| } |
| |
| void QQuickPathViewPrivate::releaseItem(QQuickItem *item) |
| { |
| if (!item || !model) |
| return; |
| qCDebug(lcItemViewDelegateLifecycle) << "release" << item; |
| QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
| itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry); |
| QQmlInstanceModel::ReleaseFlags flags = model->release(item); |
| if (!flags) { |
| // item was not destroyed, and we no longer reference it. |
| if (QQuickPathViewAttached *att = attached(item)) |
| att->setOnPath(false); |
| } else if (flags & QQmlInstanceModel::Destroyed) { |
| // but we still reference it |
| item->setParentItem(nullptr); |
| } |
| } |
| |
| QQuickPathViewAttached *QQuickPathViewPrivate::attached(QQuickItem *item) |
| { |
| return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false)); |
| } |
| |
| QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType() |
| { |
| Q_Q(QQuickPathView); |
| if (!attType) { |
| // pre-create one metatype to share with all attached objects |
| attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q)); |
| if (path) { |
| const auto attributes = path->attributes(); |
| for (const QString &attr : attributes) |
| attType->createProperty(attr.toUtf8()); |
| } |
| } |
| |
| return attType; |
| } |
| |
| void QQuickPathViewPrivate::clear() |
| { |
| if (currentItem) { |
| releaseItem(currentItem); |
| currentItem = nullptr; |
| } |
| |
| for (QQuickItem *p : qAsConst(items)) |
| releaseItem(p); |
| |
| for (QQuickItem *p : qAsConst(itemCache)) |
| releaseItem(p); |
| |
| if (requestedIndex >= 0) { |
| if (model) |
| model->cancel(requestedIndex); |
| requestedIndex = -1; |
| } |
| |
| items.clear(); |
| itemCache.clear(); |
| tl.clear(); |
| } |
| |
| void QQuickPathViewPrivate::updateMappedRange() |
| { |
| if (model && pathItems != -1 && pathItems < modelCount) { |
| mappedRange = qreal(modelCount)/pathItems; |
| mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end |
| } else { |
| mappedRange = 1; |
| mappedCache = 0; |
| } |
| } |
| |
| qreal QQuickPathViewPrivate::positionOfIndex(qreal index) const |
| { |
| qreal pos = -1; |
| |
| if (model && index >= 0 && index < modelCount) { |
| qreal start = 0; |
| if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange |
| || snapMode != QQuickPathView::NoSnap)) |
| start = highlightRangeStart; |
| qreal globalPos = index + offset; |
| globalPos = std::fmod(globalPos, qreal(modelCount)) / modelCount; |
| if (pathItems != -1 && pathItems < modelCount) { |
| globalPos += start / mappedRange; |
| globalPos = std::fmod(globalPos, qreal(1)); |
| pos = globalPos * mappedRange; |
| } else { |
| pos = std::fmod(globalPos + start, qreal(1)); |
| } |
| } |
| |
| return pos; |
| } |
| |
| // returns true if position is between lower and upper, taking into |
| // account the circular space. |
| bool QQuickPathViewPrivate::isInBound(qreal position, qreal lower, qreal upper) const |
| { |
| if (qFuzzyCompare(lower, upper)) |
| return true; |
| if (lower > upper) { |
| if (position > upper && position > lower) |
| position -= mappedRange; |
| lower -= mappedRange; |
| } |
| return position >= lower && position < upper; |
| } |
| |
| void QQuickPathViewPrivate::createHighlight() |
| { |
| Q_Q(QQuickPathView); |
| if (!q->isComponentComplete()) |
| return; |
| |
| bool changed = false; |
| if (highlightItem) { |
| highlightItem->setParentItem(nullptr); |
| highlightItem->deleteLater(); |
| highlightItem = nullptr; |
| changed = true; |
| } |
| |
| QQuickItem *item = nullptr; |
| if (highlightComponent) { |
| QQmlContext *creationContext = highlightComponent->creationContext(); |
| QQmlContext *highlightContext = new QQmlContext( |
| creationContext ? creationContext : qmlContext(q)); |
| QObject *nobj = highlightComponent->create(highlightContext); |
| if (nobj) { |
| QQml_setParent_noEvent(highlightContext, nobj); |
| item = qobject_cast<QQuickItem *>(nobj); |
| if (!item) |
| delete nobj; |
| } else { |
| delete highlightContext; |
| } |
| } else { |
| item = new QQuickItem; |
| } |
| if (item) { |
| QQml_setParent_noEvent(item, q); |
| item->setParentItem(q); |
| highlightItem = item; |
| changed = true; |
| } |
| if (changed) |
| emit q->highlightItemChanged(); |
| } |
| |
| void QQuickPathViewPrivate::updateHighlight() |
| { |
| Q_Q(QQuickPathView); |
| if (!q->isComponentComplete() || !isValid()) |
| return; |
| if (highlightItem) { |
| if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) { |
| updateItem(highlightItem, highlightRangeStart); |
| } else { |
| qreal target = currentIndex; |
| |
| offsetAdj = 0; |
| tl.reset(moveHighlight); |
| moveHighlight.setValue(highlightPosition); |
| |
| const int duration = highlightMoveDuration; |
| |
| if (target - highlightPosition > modelCount/2) { |
| highlightUp = false; |
| qreal distance = modelCount - target + highlightPosition; |
| tl.move(moveHighlight, 0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance)); |
| tl.set(moveHighlight, modelCount-0.01); |
| tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance)); |
| } else if (target - highlightPosition <= -modelCount/2) { |
| highlightUp = true; |
| qreal distance = modelCount - highlightPosition + target; |
| tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance)); |
| tl.set(moveHighlight, 0); |
| tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance)); |
| } else { |
| highlightUp = highlightPosition - target < 0; |
| tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration); |
| } |
| } |
| } |
| } |
| |
| void QQuickPathViewPrivate::setHighlightPosition(qreal pos) |
| { |
| if (!(qFuzzyCompare(pos, highlightPosition))) { |
| qreal start = 0; |
| qreal end = 1; |
| if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange) { |
| start = highlightRangeStart; |
| end = highlightRangeEnd; |
| } |
| |
| qreal range = qreal(modelCount); |
| // calc normalized position of highlight relative to offset |
| qreal relativeHighlight = std::fmod(pos + offset, range) / range; |
| |
| if (!highlightUp && relativeHighlight > end / mappedRange) { |
| qreal diff = 1 - relativeHighlight; |
| setOffset(offset + diff * range); |
| } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) { |
| qreal diff = relativeHighlight - (end - start) / mappedRange; |
| setOffset(offset - diff * range - 0.00001); |
| } |
| |
| highlightPosition = pos; |
| qreal pathPos = positionOfIndex(pos); |
| updateItem(highlightItem, pathPos); |
| if (QQuickPathViewAttached *att = attached(highlightItem)) |
| att->setOnPath(pathPos < 1); |
| } |
| } |
| |
| void QQuickPathView::pathUpdated() |
| { |
| Q_D(QQuickPathView); |
| for (QQuickItem *item : qAsConst(d->items)) { |
| if (QQuickPathViewAttached *att = d->attached(item)) |
| att->m_percent = -1; |
| } |
| refill(); |
| } |
| |
| void QQuickPathViewPrivate::updateItem(QQuickItem *item, qreal percent) |
| { |
| if (!path) |
| return; |
| if (QQuickPathViewAttached *att = attached(item)) { |
| if (qFuzzyCompare(att->m_percent, percent)) |
| return; |
| att->m_percent = percent; |
| const auto attributes = path->attributes(); |
| for (const QString &attr : attributes) |
| att->setValue(attr.toUtf8(), path->attributeAt(attr, percent)); |
| att->setOnPath(percent < 1); |
| } |
| QQuickItemPrivate::get(item)->setCulled(percent >= 1); |
| QPointF pf = path->pointAtPercent(qMin(percent, qreal(1))); |
| item->setX(pf.x() - item->width()/2); |
| item->setY(pf.y() - item->height()/2); |
| } |
| |
| void QQuickPathViewPrivate::regenerate() |
| { |
| Q_Q(QQuickPathView); |
| if (!q->isComponentComplete()) |
| return; |
| |
| clear(); |
| |
| if (!isValid()) |
| return; |
| |
| updateMappedRange(); |
| q->refill(); |
| } |
| |
| void QQuickPathViewPrivate::setDragging(bool d) |
| { |
| Q_Q(QQuickPathView); |
| if (dragging == d) |
| return; |
| |
| dragging = d; |
| if (dragging) |
| emit q->dragStarted(); |
| else |
| emit q->dragEnded(); |
| |
| emit q->draggingChanged(); |
| } |
| |
| /*! |
| \qmltype PathView |
| \instantiates QQuickPathView |
| \inqmlmodule QtQuick |
| \ingroup qtquick-paths |
| \ingroup qtquick-views |
| \inherits Item |
| \brief Lays out model-provided items on a path. |
| |
| A PathView displays data from models created from built-in QML types like ListModel |
| and XmlListModel, or custom model classes defined in C++ that inherit from |
| QAbstractListModel. |
| |
| The view has a \l model, which defines the data to be displayed, and |
| a \l delegate, which defines how the data should be displayed. |
| The \l delegate is instantiated for each item on the \l path. |
| The items may be flicked to move them along the path. |
| |
| For example, if there is a simple list model defined in a file \c ContactModel.qml like this: |
| |
| \snippet qml/pathview/ContactModel.qml 0 |
| |
| This data can be represented as a PathView, like this: |
| |
| \snippet qml/pathview/pathview.qml 0 |
| |
| \image pathview.gif |
| |
| (Note the above example uses PathAttribute to scale and modify the |
| opacity of the items as they rotate. This additional code can be seen in the |
| PathAttribute documentation.) |
| |
| PathView does not automatically handle keyboard navigation. This is because |
| the keys to use for navigation will depend upon the shape of the path. Navigation |
| can be added quite simply by setting \c focus to \c true and calling |
| \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate |
| using the left and right arrow keys: |
| |
| \qml |
| PathView { |
| // ... |
| focus: true |
| Keys.onLeftPressed: decrementCurrentIndex() |
| Keys.onRightPressed: incrementCurrentIndex() |
| } |
| \endqml |
| |
| The path view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details). |
| |
| Delegates are instantiated as needed and may be destroyed at any time. |
| State should \e never be stored in a delegate. |
| |
| PathView attaches a number of properties to the root item of the delegate, for example |
| \c {PathView.isCurrentItem}. In the following example, the root delegate item can access |
| this attached property directly as \c PathView.isCurrentItem, while the child |
| \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem. |
| |
| \snippet qml/pathview/pathview.qml 1 |
| |
| \b Note that views do not enable \e clip automatically. If the view |
| is not clipped by another item or the screen, it will be necessary |
| to set \e {clip: true} in order to have the out of view items clipped |
| nicely. |
| |
| \sa Path, {QML Data Models}, ListView, GridView, {Qt Quick Examples - Views} |
| */ |
| |
| QQuickPathView::QQuickPathView(QQuickItem *parent) |
| : QQuickItem(*(new QQuickPathViewPrivate), parent) |
| { |
| Q_D(QQuickPathView); |
| d->init(); |
| } |
| |
| QQuickPathView::~QQuickPathView() |
| { |
| Q_D(QQuickPathView); |
| d->clear(); |
| if (d->attType) |
| d->attType->release(); |
| if (d->ownModel) |
| delete d->model; |
| } |
| |
| /*! |
| \qmlattachedproperty PathView QtQuick::PathView::view |
| This attached property holds the view that manages this delegate instance. |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| /*! |
| \qmlattachedproperty bool QtQuick::PathView::onPath |
| This attached property holds whether the item is currently on the path. |
| |
| If a pathItemCount has been set, it is possible that some items may |
| be instantiated, but not considered to be currently on the path. |
| Usually, these items would be set invisible, for example: |
| |
| \qml |
| Component { |
| Rectangle { |
| visible: PathView.onPath |
| // ... |
| } |
| } |
| \endqml |
| |
| It is attached to each instance of the delegate. |
| */ |
| |
| /*! |
| \qmlattachedproperty bool QtQuick::PathView::isCurrentItem |
| This attached property is true if this delegate is the current item; otherwise false. |
| |
| It is attached to each instance of the delegate. |
| |
| This property may be used to adjust the appearance of the current item. |
| |
| \snippet qml/pathview/pathview.qml 1 |
| */ |
| |
| /*! |
| \qmlproperty model QtQuick::PathView::model |
| This property holds the model providing data for the view. |
| |
| The model provides a set of data that is used to create the items for the view. |
| For large or dynamic datasets the model is usually provided by a C++ model object. |
| Models can also be created directly in QML, using the ListModel type. |
| |
| \note changing the model will reset the offset and currentIndex to 0. |
| |
| \sa {qml-data-models}{Data Models} |
| */ |
| QVariant QQuickPathView::model() const |
| { |
| Q_D(const QQuickPathView); |
| return d->modelVariant; |
| } |
| |
| void QQuickPathView::setModel(const QVariant &m) |
| { |
| Q_D(QQuickPathView); |
| QVariant model = m; |
| if (model.userType() == qMetaTypeId<QJSValue>()) |
| model = model.value<QJSValue>().toVariant(); |
| |
| if (d->modelVariant == model) |
| return; |
| |
| if (d->model) { |
| qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), |
| this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool))); |
| qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)), |
| this, QQuickPathView, SLOT(createdItem(int,QObject*))); |
| qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)), |
| this, QQuickPathView, SLOT(initItem(int,QObject*))); |
| d->clear(); |
| } |
| |
| d->modelVariant = model; |
| QObject *object = qvariant_cast<QObject*>(model); |
| QQmlInstanceModel *vim = nullptr; |
| if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) { |
| if (d->ownModel) { |
| delete d->model; |
| d->ownModel = false; |
| } |
| d->model = vim; |
| } else { |
| if (!d->ownModel) { |
| d->model = new QQmlDelegateModel(qmlContext(this)); |
| d->ownModel = true; |
| if (isComponentComplete()) |
| static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete(); |
| } |
| if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) |
| dataModel->setModel(model); |
| } |
| int oldModelCount = d->modelCount; |
| d->modelCount = 0; |
| if (d->model) { |
| qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), |
| this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool))); |
| qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)), |
| this, QQuickPathView, SLOT(createdItem(int,QObject*))); |
| qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)), |
| this, QQuickPathView, SLOT(initItem(int,QObject*))); |
| d->modelCount = d->model->count(); |
| } |
| if (isComponentComplete()) { |
| if (d->currentIndex != 0) { |
| d->currentIndex = 0; |
| emit currentIndexChanged(); |
| } |
| if (!(qFuzzyIsNull(d->offset))) { |
| d->offset = 0; |
| emit offsetChanged(); |
| } |
| } |
| d->regenerate(); |
| if (d->modelCount != oldModelCount) |
| emit countChanged(); |
| emit modelChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::PathView::count |
| This property holds the number of items in the model. |
| */ |
| int QQuickPathView::count() const |
| { |
| Q_D(const QQuickPathView); |
| return d->model ? d->modelCount : 0; |
| } |
| |
| /*! |
| \qmlproperty Path QtQuick::PathView::path |
| This property holds the path used to lay out the items. |
| For more information see the \l Path documentation. |
| */ |
| QQuickPath *QQuickPathView::path() const |
| { |
| Q_D(const QQuickPathView); |
| return d->path; |
| } |
| |
| void QQuickPathView::setPath(QQuickPath *path) |
| { |
| Q_D(QQuickPathView); |
| if (d->path == path) |
| return; |
| if (d->path) |
| qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()), |
| this, QQuickPathView, SLOT(pathUpdated())); |
| d->path = path; |
| |
| if (path) { |
| qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()), |
| this, QQuickPathView, SLOT(pathUpdated())); |
| } |
| |
| if (isComponentComplete()) { |
| d->clear(); |
| if (d->isValid()) { |
| if (d->attType) { |
| d->attType->release(); |
| d->attType = nullptr; |
| } |
| d->regenerate(); |
| } |
| } |
| |
| emit pathChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::PathView::currentIndex |
| This property holds the index of the current item. |
| */ |
| int QQuickPathView::currentIndex() const |
| { |
| Q_D(const QQuickPathView); |
| return d->currentIndex; |
| } |
| |
| void QQuickPathView::setCurrentIndex(int idx) |
| { |
| Q_D(QQuickPathView); |
| if (!isComponentComplete()) { |
| if (idx != d->currentIndex) { |
| d->currentIndex = idx; |
| emit currentIndexChanged(); |
| } |
| return; |
| } |
| |
| idx = d->modelCount |
| ? ((idx % d->modelCount) + d->modelCount) % d->modelCount |
| : 0; |
| if (d->model && (idx != d->currentIndex || !d->currentItem)) { |
| if (d->currentItem) { |
| if (QQuickPathViewAttached *att = d->attached(d->currentItem)) |
| att->setIsCurrentItem(false); |
| d->releaseItem(d->currentItem); |
| } |
| int oldCurrentIdx = d->currentIndex; |
| QQuickItem *oldCurrentItem = d->currentItem; |
| d->currentItem = nullptr; |
| d->moveReason = QQuickPathViewPrivate::SetIndex; |
| d->currentIndex = idx; |
| if (d->modelCount) { |
| d->createCurrentItem(); |
| if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) |
| d->snapToIndex(d->currentIndex, QQuickPathViewPrivate::SetIndex); |
| d->currentItemOffset = d->positionOfIndex(d->currentIndex); |
| d->updateHighlight(); |
| } |
| if (oldCurrentIdx != d->currentIndex) |
| emit currentIndexChanged(); |
| if (oldCurrentItem != d->currentItem) |
| emit currentItemChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty Item QtQuick::PathView::currentItem |
| This property holds the current item in the view. |
| */ |
| QQuickItem *QQuickPathView::currentItem() const |
| { |
| Q_D(const QQuickPathView); |
| return d->currentItem; |
| } |
| |
| /*! |
| \qmlmethod QtQuick::PathView::incrementCurrentIndex() |
| |
| Increments the current index. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickPathView::incrementCurrentIndex() |
| { |
| Q_D(QQuickPathView); |
| d->moveDirection = QQuickPathView::Positive; |
| setCurrentIndex(currentIndex()+1); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::PathView::decrementCurrentIndex() |
| |
| Decrements the current index. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickPathView::decrementCurrentIndex() |
| { |
| Q_D(QQuickPathView); |
| d->moveDirection = QQuickPathView::Negative; |
| setCurrentIndex(currentIndex()-1); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::PathView::offset |
| |
| The offset specifies how far along the path the items are from their initial positions. |
| This is a real number that ranges from \c 0 to the count of items in the model. |
| */ |
| qreal QQuickPathView::offset() const |
| { |
| Q_D(const QQuickPathView); |
| return d->offset; |
| } |
| |
| void QQuickPathView::setOffset(qreal offset) |
| { |
| Q_D(QQuickPathView); |
| d->moveReason = QQuickPathViewPrivate::Other; |
| d->setOffset(offset); |
| d->updateCurrent(); |
| } |
| |
| void QQuickPathViewPrivate::setOffset(qreal o) |
| { |
| Q_Q(QQuickPathView); |
| if (!qFuzzyCompare(offset, o)) { |
| if (isValid() && q->isComponentComplete()) { |
| qreal oldOffset = offset; |
| offset = std::fmod(o, qreal(modelCount)); |
| if (offset < 0) |
| offset += qreal(modelCount); |
| qCDebug(lcItemViewDelegateLifecycle) << o << "was" << oldOffset << "now" << offset; |
| q->refill(); |
| } else { |
| offset = o; |
| } |
| emit q->offsetChanged(); |
| } |
| } |
| |
| void QQuickPathViewPrivate::setAdjustedOffset(qreal o) |
| { |
| setOffset(o+offsetAdj); |
| } |
| |
| /*! |
| \qmlproperty Component QtQuick::PathView::highlight |
| This property holds the component to use as the highlight. |
| |
| An instance of the highlight component will be created for each view. |
| The geometry of the resultant component instance will be managed by the view |
| so as to stay with the current item. |
| |
| The below example demonstrates how to make a simple highlight. Note the use |
| of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that |
| the highlight is hidden when flicked away from the path. |
| |
| \qml |
| Component { |
| Rectangle { |
| visible: PathView.onPath |
| // ... |
| } |
| } |
| \endqml |
| |
| \sa highlightItem, highlightRangeMode |
| */ |
| QQmlComponent *QQuickPathView::highlight() const |
| { |
| Q_D(const QQuickPathView); |
| return d->highlightComponent; |
| } |
| |
| void QQuickPathView::setHighlight(QQmlComponent *highlight) |
| { |
| Q_D(QQuickPathView); |
| if (highlight != d->highlightComponent) { |
| d->highlightComponent = highlight; |
| d->createHighlight(); |
| d->updateHighlight(); |
| emit highlightChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty Item QtQuick::PathView::highlightItem |
| |
| \c highlightItem holds the highlight item, which was created |
| from the \l highlight component. |
| |
| \sa highlight |
| */ |
| QQuickItem *QQuickPathView::highlightItem() const |
| { |
| Q_D(const QQuickPathView); |
| return d->highlightItem; |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::PathView::preferredHighlightBegin |
| \qmlproperty real QtQuick::PathView::preferredHighlightEnd |
| \qmlproperty enumeration QtQuick::PathView::highlightRangeMode |
| |
| These properties set the preferred range of the highlight (current item) |
| within the view. The preferred values must be in the range from \c 0 to \c 1. |
| |
| Valid values for \c highlightRangeMode are: |
| |
| \list |
| \li \e PathView.NoHighlightRange - no range is applied and the |
| highlight will move freely within the view. |
| \li \e PathView.ApplyRange - the view will attempt to maintain |
| the highlight within the range, however the highlight can |
| move outside of the range at the ends of the path or due to |
| a mouse interaction. |
| \li \e PathView.StrictlyEnforceRange - the highlight will never |
| move outside of the range. This means that the current item |
| will change if a keyboard or mouse action would cause the |
| highlight to move outside of the range. |
| \endlist |
| |
| The default value is \e PathView.StrictlyEnforceRange. |
| |
| Defining a highlight range is the correct way to influence where the |
| current item ends up when the view moves. For example, if you want the |
| currently selected item to be in the middle of the path, then set the |
| highlight range to be 0.5,0.5 and highlightRangeMode to \e PathView.StrictlyEnforceRange. |
| Then, when the path scrolls, |
| the currently selected item will be the item at that position. This also applies to |
| when the currently selected item changes - it will scroll to within the preferred |
| highlight range. Furthermore, the behaviour of the current item index will occur |
| whether or not a highlight exists. |
| |
| \note A valid range requires \c preferredHighlightEnd to be greater |
| than or equal to \c preferredHighlightBegin. |
| */ |
| qreal QQuickPathView::preferredHighlightBegin() const |
| { |
| Q_D(const QQuickPathView); |
| return d->highlightRangeStart; |
| } |
| |
| void QQuickPathView::setPreferredHighlightBegin(qreal start) |
| { |
| Q_D(QQuickPathView); |
| if (qFuzzyCompare(d->highlightRangeStart, start) || start < 0 || start > 1) |
| return; |
| d->highlightRangeStart = start; |
| d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd; |
| refill(); |
| emit preferredHighlightBeginChanged(); |
| } |
| |
| qreal QQuickPathView::preferredHighlightEnd() const |
| { |
| Q_D(const QQuickPathView); |
| return d->highlightRangeEnd; |
| } |
| |
| void QQuickPathView::setPreferredHighlightEnd(qreal end) |
| { |
| Q_D(QQuickPathView); |
| if (qFuzzyCompare(d->highlightRangeEnd, end) || end < 0 || end > 1) |
| return; |
| d->highlightRangeEnd = end; |
| d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd; |
| refill(); |
| emit preferredHighlightEndChanged(); |
| } |
| |
| QQuickPathView::HighlightRangeMode QQuickPathView::highlightRangeMode() const |
| { |
| Q_D(const QQuickPathView); |
| return d->highlightRangeMode; |
| } |
| |
| void QQuickPathView::setHighlightRangeMode(HighlightRangeMode mode) |
| { |
| Q_D(QQuickPathView); |
| if (d->highlightRangeMode == mode) |
| return; |
| d->highlightRangeMode = mode; |
| d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd; |
| if (d->haveHighlightRange) { |
| d->regenerate(); |
| int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex(); |
| if (index >= 0) |
| d->snapToIndex(index, QQuickPathViewPrivate::Other); |
| } |
| emit highlightRangeModeChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::PathView::highlightMoveDuration |
| This property holds the move animation duration of the highlight delegate. |
| |
| If the highlightRangeMode is StrictlyEnforceRange then this property |
| determines the speed that the items move along the path. |
| |
| The default value for the duration is 300ms. |
| */ |
| int QQuickPathView::highlightMoveDuration() const |
| { |
| Q_D(const QQuickPathView); |
| return d->highlightMoveDuration; |
| } |
| |
| void QQuickPathView::setHighlightMoveDuration(int duration) |
| { |
| Q_D(QQuickPathView); |
| if (d->highlightMoveDuration == duration) |
| return; |
| d->highlightMoveDuration = duration; |
| emit highlightMoveDurationChanged(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::PathView::dragMargin |
| This property holds the maximum distance from the path that initiates mouse dragging. |
| |
| By default the path can only be dragged by clicking on an item. If |
| dragMargin is greater than zero, a drag can be initiated by clicking |
| within dragMargin pixels of the path. |
| */ |
| qreal QQuickPathView::dragMargin() const |
| { |
| Q_D(const QQuickPathView); |
| return d->dragMargin; |
| } |
| |
| void QQuickPathView::setDragMargin(qreal dragMargin) |
| { |
| Q_D(QQuickPathView); |
| if (qFuzzyCompare(d->dragMargin, dragMargin)) |
| return; |
| d->dragMargin = dragMargin; |
| emit dragMarginChanged(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::PathView::flickDeceleration |
| This property holds the rate at which a flick will decelerate. |
| |
| The default is 100. |
| */ |
| qreal QQuickPathView::flickDeceleration() const |
| { |
| Q_D(const QQuickPathView); |
| return d->deceleration; |
| } |
| |
| void QQuickPathView::setFlickDeceleration(qreal dec) |
| { |
| Q_D(QQuickPathView); |
| if (qFuzzyCompare(d->deceleration, dec)) |
| return; |
| d->deceleration = dec; |
| emit flickDecelerationChanged(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::PathView::maximumFlickVelocity |
| This property holds the approximate maximum velocity that the user can flick the view in pixels/second. |
| |
| The default value is platform dependent. |
| */ |
| qreal QQuickPathView::maximumFlickVelocity() const |
| { |
| Q_D(const QQuickPathView); |
| return d->maximumFlickVelocity; |
| } |
| |
| void QQuickPathView::setMaximumFlickVelocity(qreal vel) |
| { |
| Q_D(QQuickPathView); |
| if (qFuzzyCompare(vel, d->maximumFlickVelocity)) |
| return; |
| d->maximumFlickVelocity = vel; |
| emit maximumFlickVelocityChanged(); |
| } |
| |
| |
| /*! |
| \qmlproperty bool QtQuick::PathView::interactive |
| |
| A user cannot drag or flick a PathView that is not interactive. |
| |
| This property is useful for temporarily disabling flicking. This allows |
| special interaction with PathView's children. |
| */ |
| bool QQuickPathView::isInteractive() const |
| { |
| Q_D(const QQuickPathView); |
| return d->interactive; |
| } |
| |
| void QQuickPathView::setInteractive(bool interactive) |
| { |
| Q_D(QQuickPathView); |
| if (interactive != d->interactive) { |
| d->interactive = interactive; |
| if (!interactive) |
| d->tl.clear(); |
| emit interactiveChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::PathView::moving |
| |
| This property holds whether the view is currently moving |
| due to the user either dragging or flicking the view. |
| */ |
| bool QQuickPathView::isMoving() const |
| { |
| Q_D(const QQuickPathView); |
| return d->moving; |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::PathView::flicking |
| |
| This property holds whether the view is currently moving |
| due to the user flicking the view. |
| */ |
| bool QQuickPathView::isFlicking() const |
| { |
| Q_D(const QQuickPathView); |
| return d->flicking; |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::PathView::dragging |
| |
| This property holds whether the view is currently moving |
| due to the user dragging the view. |
| */ |
| bool QQuickPathView::isDragging() const |
| { |
| Q_D(const QQuickPathView); |
| return d->dragging; |
| } |
| |
| /*! |
| \qmlsignal QtQuick::PathView::movementStarted() |
| |
| This signal is emitted when the view begins moving due to user |
| interaction. |
| |
| The corresponding handler is \c onMovementStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PathView::movementEnded() |
| |
| This signal is emitted when the view stops moving due to user |
| interaction. If a flick was generated, this signal will |
| be emitted once the flick stops. If a flick was not |
| generated, this signal will be emitted when the |
| user stops dragging - i.e. a mouse or touch release. |
| |
| The corresponding handler is \c onMovementEnded. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PathView::flickStarted() |
| |
| This signal is emitted when the view is flicked. A flick |
| starts from the point that the mouse or touch is released, |
| while still in motion. |
| |
| The corresponding handler is \c onFlickStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PathView::flickEnded() |
| |
| This signal is emitted when the view stops moving due to a flick. |
| |
| The corresponding handler is \c onFlickEnded. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PathView::dragStarted() |
| |
| This signal is emitted when the view starts to be dragged due to user |
| interaction. |
| |
| The corresponding handler is \c onDragStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::PathView::dragEnded() |
| |
| This signal is emitted when the user stops dragging the view. |
| |
| If the velocity of the drag is suffient at the time the |
| touch/mouse button is released then a flick will start. |
| |
| The corresponding handler is \c onDragEnded. |
| */ |
| |
| /*! |
| \qmlproperty Component QtQuick::PathView::delegate |
| |
| The delegate provides a template defining each item instantiated by the view. |
| The index is exposed as an accessible \c index property. Properties of the |
| model are also available depending upon the type of \l {qml-data-models}{Data Model}. |
| |
| The number of objects and bindings in the delegate has a direct effect on the |
| flicking performance of the view when pathItemCount is specified. If at all possible, place functionality |
| that is not needed for the normal display of the delegate in a \l Loader which |
| can load additional components when needed. |
| |
| Note that the PathView will layout the items based on the size of the root |
| item in the delegate. |
| |
| Here is an example delegate: |
| \snippet qml/pathview/pathview.qml 1 |
| */ |
| QQmlComponent *QQuickPathView::delegate() const |
| { |
| Q_D(const QQuickPathView); |
| if (d->model) { |
| if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) |
| return dataModel->delegate(); |
| } |
| |
| return nullptr; |
| } |
| |
| void QQuickPathView::setDelegate(QQmlComponent *delegate) |
| { |
| Q_D(QQuickPathView); |
| if (delegate == this->delegate()) |
| return; |
| if (!d->ownModel) { |
| d->model = new QQmlDelegateModel(qmlContext(this)); |
| d->ownModel = true; |
| if (isComponentComplete()) |
| static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete(); |
| } |
| if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) { |
| int oldCount = dataModel->count(); |
| dataModel->setDelegate(delegate); |
| d->modelCount = dataModel->count(); |
| d->regenerate(); |
| if (oldCount != dataModel->count()) |
| emit countChanged(); |
| emit delegateChanged(); |
| d->delegateValidated = false; |
| } |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::PathView::pathItemCount |
| This property holds the number of items visible on the path at any one time. |
| |
| Setting pathItemCount to undefined will show all items on the path. |
| */ |
| int QQuickPathView::pathItemCount() const |
| { |
| Q_D(const QQuickPathView); |
| return d->pathItems; |
| } |
| |
| void QQuickPathView::setPathItemCount(int i) |
| { |
| Q_D(QQuickPathView); |
| if (i == d->pathItems) |
| return; |
| if (i < 1) |
| i = 1; |
| d->pathItems = i; |
| d->updateMappedRange(); |
| if (d->isValid() && isComponentComplete()) { |
| d->regenerate(); |
| } |
| emit pathItemCountChanged(); |
| } |
| |
| void QQuickPathView::resetPathItemCount() |
| { |
| Q_D(QQuickPathView); |
| if (-1 == d->pathItems) |
| return; |
| d->pathItems = -1; |
| d->updateMappedRange(); |
| if (d->isValid() && isComponentComplete()) |
| d->regenerate(); |
| emit pathItemCountChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::PathView::cacheItemCount |
| This property holds the maximum number of items to cache off the path. |
| |
| For example, a PathView with a model containing 20 items, a pathItemCount |
| of 10, and an cacheItemCount of 4 will create up to 14 items, with 10 visible |
| on the path and 4 invisible cached items. |
| |
| The cached delegates are created asynchronously, |
| allowing creation to occur across multiple frames and reducing the |
| likelihood of skipping frames. |
| |
| \note Setting this property is not a replacement for creating efficient delegates. |
| It can improve the smoothness of scrolling behavior at the expense of additional |
| memory usage. The fewer objects and bindings in a delegate, the faster a |
| view can be scrolled. It is important to realize that setting cacheItemCount |
| will only postpone issues caused by slow-loading delegates, it is not a |
| solution for this scenario. |
| |
| \sa pathItemCount |
| */ |
| int QQuickPathView::cacheItemCount() const |
| { |
| Q_D(const QQuickPathView); |
| return d->cacheSize; |
| } |
| |
| void QQuickPathView::setCacheItemCount(int i) |
| { |
| Q_D(QQuickPathView); |
| if (i == d->cacheSize || i < 0) |
| return; |
| |
| d->cacheSize = i; |
| d->updateMappedRange(); |
| refill(); |
| emit cacheItemCountChanged(); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::PathView::snapMode |
| |
| This property determines how the items will settle following a drag or flick. |
| The possible values are: |
| |
| \list |
| \li PathView.NoSnap (default) - the items stop anywhere along the path. |
| \li PathView.SnapToItem - the items settle with an item aligned with the \l preferredHighlightBegin. |
| \li PathView.SnapOneItem - the items settle no more than one item away from the item nearest |
| \l preferredHighlightBegin at the time the press is released. This mode is particularly |
| useful for moving one page at a time. |
| \endlist |
| |
| \c snapMode does not affect the \l currentIndex. To update the |
| \l currentIndex as the view is moved, set \l highlightRangeMode |
| to \c PathView.StrictlyEnforceRange (default for PathView). |
| |
| \sa highlightRangeMode |
| */ |
| QQuickPathView::SnapMode QQuickPathView::snapMode() const |
| { |
| Q_D(const QQuickPathView); |
| return d->snapMode; |
| } |
| |
| void QQuickPathView::setSnapMode(SnapMode mode) |
| { |
| Q_D(QQuickPathView); |
| if (mode == d->snapMode) |
| return; |
| d->snapMode = mode; |
| emit snapModeChanged(); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::PathView::movementDirection |
| \since 5.7 |
| |
| This property determines the direction in which items move when setting the current index. |
| The possible values are: |
| |
| \list |
| \li PathView.Shortest (default) - the items move in the direction that requires the least |
| movement, which could be either \c Negative or \c Positive. |
| \li PathView.Negative - the items move backwards towards their destination. |
| \li PathView.Positive - the items move forwards towards their destination. |
| \endlist |
| |
| For example, suppose that there are 5 items in the model, and \l currentIndex is \c 0. |
| If currentIndex is set to \c 2, |
| |
| \list |
| \li a \c Positive movement direction will result in the following order: 0, 1, 2 |
| \li a \c Negative movement direction will result in the following order: 0, 5, 4, 3, 2 |
| \li a \c Shortest movement direction will result in same order with \c Positive . |
| \endlist |
| |
| \note this property doesn't affect the movement of \l incrementCurrentIndex() and \l decrementCurrentIndex(). |
| */ |
| QQuickPathView::MovementDirection QQuickPathView::movementDirection() const |
| { |
| Q_D(const QQuickPathView); |
| return d->movementDirection; |
| } |
| |
| void QQuickPathView::setMovementDirection(QQuickPathView::MovementDirection dir) |
| { |
| Q_D(QQuickPathView); |
| if (dir == d->movementDirection) |
| return; |
| d->movementDirection = dir; |
| if (!d->tl.isActive()) |
| d->moveDirection = d->movementDirection; |
| emit movementDirectionChanged(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::PathView::positionViewAtIndex(int index, PositionMode mode) |
| |
| Positions the view such that the \a index is at the position specified by |
| \a mode: |
| |
| \list |
| \li PathView.Beginning - position item at the beginning of the path. |
| \li PathView.Center - position item in the center of the path. |
| \li PathView.End - position item at the end of the path. |
| \li PathView.Contain - ensure the item is positioned on the path. |
| \li PathView.SnapPosition - position the item at \l preferredHighlightBegin. This mode |
| is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled |
| via \l snapMode. |
| \endlist |
| |
| \b Note: methods should only be called after the Component has completed. To position |
| the view at startup, this method should be called by Component.onCompleted. For |
| example, to position the view at the end: |
| |
| \code |
| Component.onCompleted: positionViewAtIndex(count - 1, PathView.End) |
| \endcode |
| */ |
| void QQuickPathView::positionViewAtIndex(int index, int mode) |
| { |
| Q_D(QQuickPathView); |
| if (!d->isValid()) |
| return; |
| if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView |
| return; |
| |
| if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems)) |
| return; |
| |
| int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount); |
| int idx = (index+d->modelCount) % d->modelCount; |
| bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange |
| || d->snapMode != QQuickPathView::NoSnap); |
| |
| qreal beginOffset; |
| qreal endOffset; |
| if (snap) { |
| beginOffset = d->modelCount - idx - qFloor(count * d->highlightRangeStart); |
| endOffset = beginOffset + count - 1; |
| } else { |
| beginOffset = d->modelCount - idx; |
| // Small offset since the last point coincides with the first and |
| // this the only "end" position that gives the expected visual result. |
| qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001; |
| endOffset = std::fmod(beginOffset + count, qreal(d->modelCount)) - adj; |
| } |
| qreal offset = d->offset; |
| switch (mode) { |
| case Beginning: |
| offset = beginOffset; |
| break; |
| case End: |
| offset = endOffset; |
| break; |
| case Center: |
| if (beginOffset < endOffset) |
| offset = (beginOffset + endOffset)/2; |
| else |
| offset = (beginOffset + (endOffset + d->modelCount))/2; |
| if (snap) |
| offset = qRound(offset); |
| break; |
| case Contain: |
| if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset)) |
| || (d->offset < beginOffset && d->offset > endOffset)) { |
| qreal diff1 = std::fmod(beginOffset - d->offset + d->modelCount, qreal(d->modelCount)); |
| qreal diff2 = std::fmod(d->offset - endOffset + d->modelCount, qreal(d->modelCount)); |
| if (diff1 < diff2) |
| offset = beginOffset; |
| else |
| offset = endOffset; |
| } |
| break; |
| case SnapPosition: |
| offset = d->modelCount - idx; |
| break; |
| } |
| |
| d->tl.clear(); |
| setOffset(offset); |
| } |
| |
| /*! |
| \qmlmethod int QtQuick::PathView::indexAt(real x, real y) |
| |
| Returns the index of the item containing the point \a x, \a y in content |
| coordinates. If there is no item at the point specified, -1 is returned. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| int QQuickPathView::indexAt(qreal x, qreal y) const |
| { |
| Q_D(const QQuickPathView); |
| QQuickItem *item = itemAt(x, y); |
| return item ? d->model->indexOf(item, nullptr) : -1; |
| } |
| |
| /*! |
| \qmlmethod Item QtQuick::PathView::itemAt(real x, real y) |
| |
| Returns the item containing the point \a x, \a y in content |
| coordinates. If there is no item at the point specified, null is returned. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| QQuickItem *QQuickPathView::itemAt(qreal x, qreal y) const |
| { |
| Q_D(const QQuickPathView); |
| if (!d->isValid()) |
| return nullptr; |
| |
| for (QQuickItem *item : d->items) { |
| QPointF p = item->mapFromItem(this, QPointF(x, y)); |
| if (item->contains(p)) |
| return item; |
| } |
| |
| return nullptr; |
| } |
| |
| /*! |
| \qmlmethod Item QtQuick::QQuickPathView::itemAtIndex(int index) |
| |
| Returns the item for \a index. If there is no item for that index, for example |
| because it has not been created yet, or because it has been panned out of |
| the visible area and removed from the cache, null is returned. |
| |
| \b Note: this method should only be called after the Component has completed. |
| The returned value should also not be stored since it can turn to null |
| as soon as control goes out of the calling scope, if the view releases that item. |
| |
| \since 5.13 |
| */ |
| QQuickItem *QQuickPathView::itemAtIndex(int index) const |
| { |
| Q_D(const QQuickPathView); |
| if (!d->isValid()) |
| return nullptr; |
| |
| for (QQuickItem *item : d->items) { |
| if (index == d->model->indexOf(item, nullptr)) |
| return item; |
| } |
| |
| return nullptr; |
| } |
| |
| QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const |
| { |
| const auto pathLength = path->path().length(); |
| qreal samples = qMin(pathLength / 5, qreal(500)); |
| qreal res = pathLength / samples; |
| |
| qreal mindist = 1e10; // big number |
| QPointF nearPoint = path->pointAtPercent(0); |
| qreal nearPc = 0; |
| |
| // get rough pos |
| for (qreal i=1; i < samples; i++) { |
| QPointF pt = path->pointAtPercent(i/samples); |
| QPointF diff = pt - point; |
| qreal dist = diff.x()*diff.x() + diff.y()*diff.y(); |
| if (dist < mindist) { |
| nearPoint = pt; |
| nearPc = i; |
| mindist = dist; |
| } |
| } |
| |
| // now refine |
| qreal approxPc = nearPc; |
| for (qreal i = approxPc-1; i < approxPc+1; i += 1/(2*res)) { |
| QPointF pt = path->pointAtPercent(i/samples); |
| QPointF diff = pt - point; |
| qreal dist = diff.x()*diff.x() + diff.y()*diff.y(); |
| if (dist < mindist) { |
| nearPoint = pt; |
| nearPc = i; |
| mindist = dist; |
| } |
| } |
| |
| if (nearPercent) |
| *nearPercent = nearPc / samples; |
| |
| return nearPoint; |
| } |
| |
| void QQuickPathViewPrivate::addVelocitySample(qreal v) |
| { |
| velocityBuffer.append(v); |
| if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER) |
| velocityBuffer.remove(0); |
| qCDebug(lcPathView) << "instantaneous velocity" << v; |
| } |
| |
| qreal QQuickPathViewPrivate::calcVelocity() const |
| { |
| qreal velocity = 0; |
| if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) { |
| int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES; |
| for (int i = 0; i < count; ++i) { |
| qreal v = velocityBuffer.at(i); |
| velocity += v; |
| } |
| velocity /= count; |
| qCDebug(lcPathView) << "average velocity" << velocity << "based on" << count << "samples"; |
| } |
| return velocity; |
| } |
| |
| qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event) const |
| { |
| if (0 != event->timestamp()) |
| return qint64(event->timestamp()); |
| return timer.elapsed(); |
| } |
| |
| void QQuickPathView::mousePressEvent(QMouseEvent *event) |
| { |
| Q_D(QQuickPathView); |
| if (d->interactive) { |
| d->handleMousePressEvent(event); |
| event->accept(); |
| } else { |
| QQuickItem::mousePressEvent(event); |
| } |
| } |
| |
| void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event) |
| { |
| Q_Q(QQuickPathView); |
| if (!interactive || !items.count() || !model || !modelCount) |
| return; |
| velocityBuffer.clear(); |
| int idx = 0; |
| for (; idx < items.count(); ++idx) { |
| QQuickItem *item = items.at(idx); |
| if (item->contains(item->mapFromScene(event->windowPos()))) |
| break; |
| } |
| if (idx == items.count() && qFuzzyIsNull(dragMargin)) // didn't click on an item |
| return; |
| |
| startPoint = pointNear(event->localPos(), &startPc); |
| startPos = event->localPos(); |
| if (idx == items.count()) { |
| qreal distance = qAbs(event->localPos().x() - startPoint.x()) + qAbs(event->localPos().y() - startPoint.y()); |
| if (distance > dragMargin) |
| return; |
| } |
| |
| if (tl.isActive() && flicking && flickDuration && qreal(tl.time()) / flickDuration < 0.8) { |
| stealMouse = true; // If we've been flicked then steal the click. |
| q->grabMouse(); // grab it right now too, just to be sure (QTBUG-77173) |
| } else { |
| stealMouse = false; |
| } |
| q->setKeepMouseGrab(stealMouse); |
| |
| timer.start(); |
| lastPosTime = computeCurrentTime(event); |
| tl.clear(); |
| } |
| |
| void QQuickPathView::mouseMoveEvent(QMouseEvent *event) |
| { |
| Q_D(QQuickPathView); |
| if (d->interactive) { |
| d->handleMouseMoveEvent(event); |
| event->accept(); |
| } else { |
| QQuickItem::mouseMoveEvent(event); |
| } |
| } |
| |
| void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event) |
| { |
| Q_Q(QQuickPathView); |
| if (!interactive || !timer.isValid() || !model || !modelCount) |
| return; |
| |
| qint64 currentTimestamp = computeCurrentTime(event); |
| qreal newPc; |
| QPointF pathPoint = pointNear(event->localPos(), &newPc); |
| if (!stealMouse) { |
| QPointF posDelta = event->localPos() - startPos; |
| if (QQuickWindowPrivate::dragOverThreshold(posDelta.y(), Qt::YAxis, event) || QQuickWindowPrivate::dragOverThreshold(posDelta.x(), Qt::XAxis, event)) { |
| // The touch has exceeded the threshold. If the movement along the path is close to the drag threshold |
| // then we'll assume that this gesture targets the PathView. This ensures PathView gesture grabbing |
| // is in sync with other items. |
| QPointF pathDelta = pathPoint - startPoint; |
| const int startDragDistance = QGuiApplication::styleHints()->startDragDistance(); |
| if (qAbs(pathDelta.x()) > startDragDistance * 0.8 |
| || qAbs(pathDelta.y()) > startDragDistance * 0.8) { |
| stealMouse = true; |
| q->setKeepMouseGrab(true); |
| } |
| } |
| } else { |
| moveReason = QQuickPathViewPrivate::Mouse; |
| int count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount); |
| qreal diff = (newPc - startPc)*count; |
| if (!qFuzzyIsNull(diff)) { |
| q->setOffset(offset + diff); |
| |
| if (diff > modelCount/2) |
| diff -= modelCount; |
| else if (diff < -modelCount/2) |
| diff += modelCount; |
| |
| qint64 elapsed = currentTimestamp - lastPosTime; |
| if (elapsed > 0) |
| addVelocitySample(diff / (qreal(elapsed) / 1000)); |
| } |
| if (!moving) { |
| moving = true; |
| emit q->movingChanged(); |
| emit q->movementStarted(); |
| } |
| setDragging(true); |
| } |
| startPc = newPc; |
| lastPosTime = currentTimestamp; |
| } |
| |
| void QQuickPathView::mouseReleaseEvent(QMouseEvent *event) |
| { |
| Q_D(QQuickPathView); |
| if (d->interactive) { |
| d->handleMouseReleaseEvent(event); |
| event->accept(); |
| ungrabMouse(); |
| } else { |
| QQuickItem::mouseReleaseEvent(event); |
| } |
| } |
| |
| void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *event) |
| { |
| Q_Q(QQuickPathView); |
| stealMouse = false; |
| q->setKeepMouseGrab(false); |
| setDragging(false); |
| if (!interactive || !timer.isValid() || !model || !modelCount) { |
| timer.invalidate(); |
| if (!tl.isActive()) |
| q->movementEnding(); |
| return; |
| } |
| |
| qreal velocity = calcVelocity(); |
| qint64 elapsed = computeCurrentTime(event) - lastPosTime; |
| // Let the velocity linearly decay such that it becomes 0 if elapsed time > QML_FLICK_VELOCITY_DECAY_TIME |
| // The intention is that if you are flicking at some speed, then stop in one place for some time before releasing, |
| // the previous velocity is lost. (QTBUG-77173, QTBUG-59052) |
| velocity *= qreal(qMax(0LL, QML_FLICK_VELOCITY_DECAY_TIME - elapsed)) / QML_FLICK_VELOCITY_DECAY_TIME; |
| qCDebug(lcPathView) << "after elapsed time" << elapsed << "velocity decayed to" << velocity; |
| qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount); |
| const auto averageItemLength = path->path().length() / count; |
| qreal pixelVelocity = averageItemLength * velocity; |
| if (qAbs(pixelVelocity) > MinimumFlickVelocity) { |
| if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) { |
| // limit velocity |
| qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity; |
| velocity = maxVel / averageItemLength; |
| } |
| // Calculate the distance to be travelled |
| qreal v2 = velocity*velocity; |
| qreal accel = deceleration/10; |
| qreal dist = 0; |
| if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange |
| || snapMode != QQuickPathView::NoSnap)) { |
| if (snapMode == QQuickPathView::SnapOneItem) { |
| // encourage snapping one item in direction of motion |
| if (velocity > 0) |
| dist = qRound(0.5 + offset) - offset; |
| else |
| dist = qRound(0.5 - offset) + offset; |
| } else { |
| // + 0.25 to encourage moving at least one item in the flick direction |
| dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2) + 0.25)); |
| |
| // round to nearest item. |
| if (velocity > 0) |
| dist = qRound(dist + offset) - offset; |
| else |
| dist = qRound(dist - offset) + offset; |
| } |
| // Calculate accel required to stop on item boundary |
| if (dist <= 0) { |
| dist = 0; |
| accel = 0; |
| } else { |
| accel = v2 / (2 * qAbs(dist)); |
| } |
| } else { |
| dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2))); |
| } |
| flickDuration = int(1000 * qAbs(velocity) / accel); |
| offsetAdj = 0; |
| moveOffset.setValue(offset); |
| tl.accel(moveOffset, velocity, accel, dist); |
| tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this)); |
| if (!flicking) { |
| flicking = true; |
| emit q->flickingChanged(); |
| emit q->flickStarted(); |
| } |
| } else { |
| fixOffset(); |
| } |
| |
| timer.invalidate(); |
| if (!tl.isActive()) |
| q->movementEnding(); |
| } |
| |
| bool QQuickPathView::sendMouseEvent(QMouseEvent *event) |
| { |
| Q_D(QQuickPathView); |
| QPointF localPos = mapFromScene(event->windowPos()); |
| |
| QQuickWindow *c = window(); |
| QQuickItem *grabber = c ? c->mouseGrabberItem() : nullptr; |
| if (grabber == this && d->stealMouse) { |
| // we are already the grabber and we do want the mouse event to ourselves. |
| return true; |
| } |
| |
| bool grabberDisabled = grabber && !grabber->isEnabled(); |
| bool stealThisEvent = d->stealMouse; |
| if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab() || grabberDisabled)) { |
| QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); |
| mouseEvent->setAccepted(false); |
| |
| switch (mouseEvent->type()) { |
| case QEvent::MouseMove: |
| d->handleMouseMoveEvent(mouseEvent.data()); |
| break; |
| case QEvent::MouseButtonPress: |
| d->handleMousePressEvent(mouseEvent.data()); |
| stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above |
| break; |
| case QEvent::MouseButtonRelease: |
| d->handleMouseReleaseEvent(mouseEvent.data()); |
| break; |
| default: |
| break; |
| } |
| grabber = c ? c->mouseGrabberItem() : nullptr; |
| if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || grabberDisabled) { |
| grabMouse(); |
| } |
| |
| const bool filtered = stealThisEvent || grabberDisabled; |
| if (filtered) { |
| event->setAccepted(false); |
| } |
| return filtered; |
| } else if (d->timer.isValid()) { |
| d->timer.invalidate(); |
| d->fixOffset(); |
| } |
| if (event->type() == QEvent::MouseButtonRelease || (grabber && grabber->keepMouseGrab() && !grabberDisabled)) { |
| d->stealMouse = false; |
| } |
| return false; |
| } |
| |
| bool QQuickPathView::childMouseEventFilter(QQuickItem *i, QEvent *e) |
| { |
| Q_D(QQuickPathView); |
| if (!isVisible() || !d->interactive) |
| return QQuickItem::childMouseEventFilter(i, e); |
| |
| switch (e->type()) { |
| case QEvent::MouseButtonPress: |
| case QEvent::MouseMove: |
| case QEvent::MouseButtonRelease: |
| return sendMouseEvent(static_cast<QMouseEvent *>(e)); |
| default: |
| break; |
| } |
| |
| return QQuickItem::childMouseEventFilter(i, e); |
| } |
| |
| void QQuickPathView::mouseUngrabEvent() |
| { |
| Q_D(QQuickPathView); |
| if (d->stealMouse || |
| (!d->flicking && d->snapMode != NoSnap && !qFuzzyCompare(qRound(d->offset), d->offset))) { |
| // if our mouse grab has been removed (probably by a Flickable), |
| // or if we should snap but haven't done it, fix our state |
| d->stealMouse = false; |
| setKeepMouseGrab(false); |
| d->timer.invalidate(); |
| d->fixOffset(); |
| d->setDragging(false); |
| if (!d->tl.isActive()) |
| movementEnding(); |
| } |
| } |
| |
| void QQuickPathView::updatePolish() |
| { |
| QQuickItem::updatePolish(); |
| refill(); |
| } |
| |
| static inline int currentIndexRemainder(int currentIndex, int modelCount) Q_DECL_NOTHROW |
| { |
| if (currentIndex < 0) |
| return modelCount + currentIndex % modelCount; |
| else |
| return currentIndex % modelCount; |
| } |
| |
| void QQuickPathView::componentComplete() |
| { |
| Q_D(QQuickPathView); |
| if (d->model && d->ownModel) |
| static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete(); |
| |
| QQuickItem::componentComplete(); |
| |
| if (d->model) { |
| d->modelCount = d->model->count(); |
| if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex |
| d->offset = std::fmod(qreal(d->modelCount - currentIndexRemainder(d->currentIndex, d->modelCount)), qreal(d->modelCount)); |
| } |
| |
| d->createHighlight(); |
| d->regenerate(); |
| d->updateHighlight(); |
| d->updateCurrent(); |
| |
| if (d->modelCount) |
| emit countChanged(); |
| } |
| |
| void QQuickPathView::refill() |
| { |
| Q_D(QQuickPathView); |
| |
| if (d->inRefill) { |
| d->scheduleLayout(); |
| return; |
| } |
| |
| d->layoutScheduled = false; |
| |
| if (!d->isValid() || !isComponentComplete()) |
| return; |
| |
| d->inRefill = true; |
| |
| bool currentVisible = false; |
| int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount); |
| |
| // first move existing items and remove items off path |
| qCDebug(lcItemViewDelegateLifecycle) << "currentIndex" << d->currentIndex << "offset" << d->offset; |
| QList<QQuickItem*>::iterator it = d->items.begin(); |
| while (it != d->items.end()) { |
| QQuickItem *item = *it; |
| int idx = d->model->indexOf(item, nullptr); |
| qreal pos = d->positionOfIndex(idx); |
| if (lcItemViewDelegateLifecycle().isDebugEnabled()) { |
| QQuickText *text = qmlobject_cast<QQuickText*>(item); |
| if (text) |
| qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ": QQuickText" << text->objectName() << text->text().leftRef(40); |
| else |
| qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ":" << item; |
| } |
| if (pos < 1) { |
| d->updateItem(item, pos); |
| if (idx == d->currentIndex) { |
| currentVisible = true; |
| d->currentItemOffset = pos; |
| } |
| ++it; |
| } else { |
| d->updateItem(item, pos); |
| if (QQuickPathViewAttached *att = d->attached(item)) |
| att->setOnPath(pos < 1); |
| if (!d->isInBound(pos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) { |
| qCDebug(lcItemViewDelegateLifecycle) << "release" << idx << "@" << pos << ", !isInBound: lower" << (d->mappedRange - d->mappedCache) << "upper" << (1 + d->mappedCache); |
| d->releaseItem(item); |
| it = d->items.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| bool waiting = false; |
| if (d->modelCount) { |
| // add items as needed |
| if (d->items.count() < count+d->cacheSize) { |
| int endIdx = 0; |
| qreal endPos; |
| int startIdx = 0; |
| qreal startPos = 0; |
| const bool wasEmpty = d->items.isEmpty(); |
| if (!wasEmpty) { |
| //Find the beginning and end, items may not be in sorted order |
| endPos = -1; |
| startPos = 2; |
| |
| for (QQuickItem * item : qAsConst(d->items)) { |
| int idx = d->model->indexOf(item, nullptr); |
| qreal curPos = d->positionOfIndex(idx); |
| if (curPos > endPos) { |
| endPos = curPos; |
| endIdx = idx; |
| } |
| |
| if (curPos < startPos) { |
| startPos = curPos; |
| startIdx = idx; |
| } |
| } |
| } else { |
| if (d->haveHighlightRange |
| && (d->highlightRangeMode != QQuickPathView::NoHighlightRange |
| || d->snapMode != QQuickPathView::NoSnap)) |
| startPos = d->highlightRangeStart; |
| // With no items, then "end" is just off the top so we populate via append |
| endIdx = (qRound(d->modelCount - d->offset) - 1) % d->modelCount; |
| endPos = d->positionOfIndex(endIdx); |
| } |
| //Append |
| int idx = endIdx + 1; |
| if (idx >= d->modelCount) |
| idx = 0; |
| qreal nextPos = d->positionOfIndex(idx); |
| while ((d->isInBound(nextPos, endPos, 1 + d->mappedCache) || !d->items.count()) |
| && d->items.count() < count+d->cacheSize) { |
| qCDebug(lcItemViewDelegateLifecycle) << "append" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.count(); |
| QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1); |
| if (!item) { |
| waiting = true; |
| break; |
| } |
| if (d->items.contains(item)) { |
| d->releaseItem(item); |
| break; //Otherwise we'd "re-add" it, and get confused |
| } |
| if (d->currentIndex == idx) { |
| currentVisible = true; |
| d->currentItemOffset = nextPos; |
| } |
| d->items.append(item); |
| d->updateItem(item, nextPos); |
| endIdx = idx; |
| endPos = nextPos; |
| ++idx; |
| if (idx >= d->modelCount) |
| idx = 0; |
| nextPos = d->positionOfIndex(idx); |
| } |
| |
| //Prepend |
| idx = (wasEmpty ? d->calcCurrentIndex() : startIdx) - 1; |
| |
| if (idx < 0) |
| idx = d->modelCount - 1; |
| nextPos = d->positionOfIndex(idx); |
| while (!waiting && d->isInBound(nextPos, d->mappedRange - d->mappedCache, startPos) |
| && d->items.count() < count+d->cacheSize) { |
| qCDebug(lcItemViewDelegateLifecycle) << "prepend" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.count(); |
| QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1); |
| if (!item) { |
| waiting = true; |
| break; |
| } |
| if (d->items.contains(item)) { |
| d->releaseItem(item); |
| break; //Otherwise we'd "re-add" it, and get confused |
| } |
| if (d->currentIndex == idx) { |
| currentVisible = true; |
| d->currentItemOffset = nextPos; |
| } |
| d->items.prepend(item); |
| d->updateItem(item, nextPos); |
| startIdx = idx; |
| startPos = nextPos; |
| --idx; |
| if (idx < 0) |
| idx = d->modelCount - 1; |
| nextPos = d->positionOfIndex(idx); |
| } |
| |
| // In rare cases, when jumping around with pathCount close to modelCount, |
| // new items appear in the middle. This more generic addition iteration handles this |
| // Since this is the rare case, we try append/prepend first and only do this if |
| // there are gaps still left to fill. |
| if (!waiting && d->items.count() < count+d->cacheSize) { |
| qCDebug(lcItemViewDelegateLifecycle) << "Checking for pathview middle inserts, items count was" << d->items.count(); |
| idx = startIdx; |
| QQuickItem *lastItem = d->items.at(0); |
| while (idx != endIdx) { |
| nextPos = d->positionOfIndex(idx); |
| if (d->isInBound(nextPos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) { |
| //This gets the reference from the delegate model, and will not re-create |
| QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1); |
| if (!item) { |
| waiting = true; |
| break; |
| } |
| |
| if (!d->items.contains(item)) { //We found a hole |
| qCDebug(lcItemViewDelegateLifecycle) << "middle insert" << idx << "@" << nextPos |
| << (d->currentIndex == idx ? "current" : "") |
| << "items count was" << d->items.count(); |
| if (d->currentIndex == idx) { |
| currentVisible = true; |
| d->currentItemOffset = nextPos; |
| } |
| int lastListIdx = d->items.indexOf(lastItem); |
| d->items.insert(lastListIdx + 1, item); |
| d->updateItem(item, nextPos); |
| } else { |
| d->releaseItem(item); |
| } |
| |
| lastItem = item; |
| } |
| |
| ++idx; |
| if (idx >= d->modelCount) |
| idx = 0; |
| } |
| } |
| } |
| } |
| |
| bool currentChanged = false; |
| if (!currentVisible) { |
| d->currentItemOffset = 1; |
| if (d->currentItem) { |
| d->updateItem(d->currentItem, 1); |
| } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) { |
| if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) { |
| currentChanged = true; |
| d->updateItem(d->currentItem, 1); |
| if (QQuickPathViewAttached *att = d->attached(d->currentItem)) |
| att->setIsCurrentItem(true); |
| } |
| } |
| } else if (!waiting && !d->currentItem) { |
| if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) { |
| currentChanged = true; |
| d->currentItem->setFocus(true); |
| if (QQuickPathViewAttached *att = d->attached(d->currentItem)) |
| att->setIsCurrentItem(true); |
| } |
| } |
| |
| if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) { |
| d->updateItem(d->highlightItem, d->highlightRangeStart); |
| if (QQuickPathViewAttached *att = d->attached(d->highlightItem)) |
| att->setOnPath(true); |
| } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) { |
| d->updateItem(d->highlightItem, d->currentItemOffset); |
| if (QQuickPathViewAttached *att = d->attached(d->highlightItem)) |
| att->setOnPath(currentVisible); |
| } |
| for (QQuickItem *item : qAsConst(d->itemCache)) |
| d->releaseItem(item); |
| d->itemCache.clear(); |
| |
| d->inRefill = false; |
| if (currentChanged) |
| emit currentItemChanged(); |
| } |
| |
| void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset) |
| { |
| Q_D(QQuickPathView); |
| if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete()) |
| return; |
| |
| if (reset) { |
| d->modelCount = d->model->count(); |
| d->regenerate(); |
| emit countChanged(); |
| return; |
| } |
| |
| if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty()) |
| return; |
| |
| const int modelCount = d->modelCount; |
| int moveId = -1; |
| int moveOffset = 0; |
| bool currentChanged = false; |
| bool changedOffset = false; |
| for (const QQmlChangeSet::Change &r : changeSet.removes()) { |
| if (moveId == -1 && d->currentIndex >= r.index + r.count) { |
| d->currentIndex -= r.count; |
| currentChanged = true; |
| } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) { |
| // current item has been removed. |
| if (r.isMove()) { |
| moveId = r.moveId; |
| moveOffset = d->currentIndex - r.index; |
| } else if (d->currentItem) { |
| if (QQuickPathViewAttached *att = d->attached(d->currentItem)) |
| att->setIsCurrentItem(true); |
| d->releaseItem(d->currentItem); |
| d->currentItem = nullptr; |
| } |
| d->currentIndex = qMin(r.index, d->modelCount - r.count - 1); |
| currentChanged = true; |
| } |
| |
| if (r.index > d->currentIndex) { |
| changedOffset = true; |
| d->offset -= r.count; |
| d->offsetAdj -= r.count; |
| } |
| d->modelCount -= r.count; |
| } |
| for (const QQmlChangeSet::Change &i : changeSet.inserts()) { |
| if (d->modelCount) { |
| if (moveId == -1 && i.index <= d->currentIndex) { |
| d->currentIndex += i.count; |
| currentChanged = true; |
| } else { |
| if (moveId != -1 && moveId == i.moveId) { |
| d->currentIndex = i.index + moveOffset; |
| currentChanged = true; |
| } |
| if (i.index > d->currentIndex) { |
| d->offset += i.count; |
| d->offsetAdj += i.count; |
| changedOffset = true; |
| } |
| } |
| } |
| d->modelCount += i.count; |
| } |
| |
| d->offset = std::fmod(d->offset, qreal(d->modelCount)); |
| if (d->offset < 0) |
| d->offset += d->modelCount; |
| if (d->currentIndex == -1) |
| d->currentIndex = d->calcCurrentIndex(); |
| |
| d->itemCache += d->items; |
| d->items.clear(); |
| |
| if (!d->modelCount) { |
| for (QQuickItem * item : qAsConst(d->itemCache)) |
| d->releaseItem(item); |
| d->itemCache.clear(); |
| d->offset = 0; |
| changedOffset = true; |
| d->tl.reset(d->moveOffset); |
| } else { |
| if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) { |
| d->offset = std::fmod(qreal(d->modelCount - d->currentIndex), qreal(d->modelCount)); |
| changedOffset = true; |
| } |
| d->updateMappedRange(); |
| d->scheduleLayout(); |
| } |
| if (changedOffset) |
| emit offsetChanged(); |
| if (currentChanged) |
| emit currentIndexChanged(); |
| if (d->modelCount != modelCount) |
| emit countChanged(); |
| } |
| |
| void QQuickPathView::destroyingItem(QObject *item) |
| { |
| Q_UNUSED(item); |
| } |
| |
| void QQuickPathView::ticked() |
| { |
| Q_D(QQuickPathView); |
| d->updateCurrent(); |
| } |
| |
| void QQuickPathView::movementEnding() |
| { |
| Q_D(QQuickPathView); |
| if (d->flicking) { |
| d->flicking = false; |
| emit flickingChanged(); |
| emit flickEnded(); |
| } |
| if (d->moving && !d->stealMouse) { |
| d->moving = false; |
| emit movingChanged(); |
| emit movementEnded(); |
| } |
| d->moveDirection = d->movementDirection; |
| } |
| |
| // find the item closest to the snap position |
| int QQuickPathViewPrivate::calcCurrentIndex() |
| { |
| int current = 0; |
| if (modelCount && model && items.count()) { |
| offset = std::fmod(offset, qreal(modelCount)); |
| if (offset < 0) |
| offset += modelCount; |
| current = qRound(qAbs(std::fmod(modelCount - offset, qreal(modelCount)))); |
| current = current % modelCount; |
| } |
| |
| return current; |
| } |
| |
| void QQuickPathViewPrivate::createCurrentItem() |
| { |
| if (requestedIndex != -1) |
| return; |
| |
| bool inItems = false; |
| for (QQuickItem *item : qAsConst(items)) { |
| if (model->indexOf(item, nullptr) == currentIndex) { |
| inItems = true; |
| break; |
| } |
| } |
| |
| if (inItems) { |
| if ((currentItem = getItem(currentIndex, currentIndex))) { |
| currentItem->setFocus(true); |
| if (QQuickPathViewAttached *att = attached(currentItem)) |
| att->setIsCurrentItem(true); |
| } |
| } else if (currentIndex >= 0 && currentIndex < modelCount) { |
| if ((currentItem = getItem(currentIndex, currentIndex))) { |
| updateItem(currentItem, 1); |
| if (QQuickPathViewAttached *att = attached(currentItem)) |
| att->setIsCurrentItem(true); |
| } |
| } |
| } |
| |
| void QQuickPathViewPrivate::updateCurrent() |
| { |
| Q_Q(QQuickPathView); |
| if (moveReason == SetIndex) |
| return; |
| if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange) |
| return; |
| |
| int idx = calcCurrentIndex(); |
| if (model && (idx != currentIndex || !currentItem)) { |
| if (currentItem) { |
| if (QQuickPathViewAttached *att = attached(currentItem)) |
| att->setIsCurrentItem(false); |
| releaseItem(currentItem); |
| } |
| int oldCurrentIndex = currentIndex; |
| currentIndex = idx; |
| currentItem = nullptr; |
| createCurrentItem(); |
| if (oldCurrentIndex != currentIndex) |
| emit q->currentIndexChanged(); |
| emit q->currentItemChanged(); |
| } |
| } |
| |
| void QQuickPathViewPrivate::fixOffsetCallback(void *d) |
| { |
| static_cast<QQuickPathViewPrivate *>(d)->fixOffset(); |
| } |
| |
| void QQuickPathViewPrivate::fixOffset() |
| { |
| Q_Q(QQuickPathView); |
| if (model && items.count()) { |
| if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange |
| || snapMode != QQuickPathView::NoSnap)) { |
| int curr = calcCurrentIndex(); |
| if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) |
| q->setCurrentIndex(curr); |
| else |
| snapToIndex(curr, Other); |
| } |
| } |
| } |
| |
| void QQuickPathViewPrivate::snapToIndex(int index, MovementReason reason) |
| { |
| if (!model || modelCount <= 0) |
| return; |
| |
| qreal targetOffset = std::fmod(qreal(modelCount - index), qreal(modelCount)); |
| moveReason = reason; |
| offsetAdj = 0; |
| tl.reset(moveOffset); |
| moveOffset.setValue(offset); |
| |
| const int duration = highlightMoveDuration; |
| |
| const qreal count = pathItems == -1 ? modelCount : qMin(pathItems, modelCount); |
| const qreal averageItemLength = path->path().length() / count; |
| const qreal threshold = 0.5 / averageItemLength; // if we are within .5 px, we want to immediately assign rather than animate |
| |
| if (!duration || qAbs(offset - targetOffset) < threshold || (qFuzzyIsNull(targetOffset) && qAbs(modelCount - offset) < threshold)) { |
| tl.set(moveOffset, targetOffset); |
| } else if (moveDirection == QQuickPathView::Positive || (moveDirection == QQuickPathView::Shortest && targetOffset - offset > modelCount/2)) { |
| qreal distance = modelCount - targetOffset + offset; |
| if (targetOffset > moveOffset) { |
| tl.move(moveOffset, 0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance)); |
| tl.set(moveOffset, modelCount); |
| tl.move(moveOffset, targetOffset, QEasingCurve(qFuzzyIsNull(offset) ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance)); |
| } else { |
| tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration); |
| } |
| } else if (moveDirection == QQuickPathView::Negative || targetOffset - offset <= -modelCount/2) { |
| qreal distance = modelCount - offset + targetOffset; |
| if (targetOffset < moveOffset) { |
| tl.move(moveOffset, modelCount, QEasingCurve(qFuzzyIsNull(targetOffset) ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance)); |
| tl.set(moveOffset, 0); |
| tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance)); |
| } else { |
| tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration); |
| } |
| } else { |
| tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration); |
| } |
| } |
| |
| QQuickPathViewAttached *QQuickPathView::qmlAttachedProperties(QObject *obj) |
| { |
| return new QQuickPathViewAttached(obj); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickpathview_p.cpp" |