/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
** Contact: http://www.qt-project.org/
**
-** This file is part of the QtDeclarative module of the Qt Toolkit.
+** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
**
**
**
+**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qquickpathview_p.h"
#include "qquickpathview_p_p.h"
-#include "qquickcanvas.h"
+#include "qquickwindow.h"
-#include <QtQuick/private/qdeclarativestate_p.h>
-#include <private/qdeclarativeopenmetaobject_p.h>
+#include <QtQuick/private/qquickstate_p.h>
+#include <private/qqmlglobal_p.h>
+#include <private/qqmlopenmetaobject_p.h>
#include <private/qlistmodelinterface_p.h>
-#include <private/qdeclarativechangeset_p.h>
+#include <private/qquickchangeset_p.h>
#include <QtGui/qevent.h>
#include <QtGui/qevent.h>
#include <QtCore/qmath.h>
#include <math.h>
+// The number of samples to use in calculating the velocity of a flick
+#ifndef QML_FLICK_SAMPLEBUFFER
+#define QML_FLICK_SAMPLEBUFFER 1
+#endif
+
+// The number of samples to discard when calculating the flick velocity.
+// Touch panels often produce inaccurate results as the finger is lifted.
+#ifndef QML_FLICK_DISCARDSAMPLES
+#define QML_FLICK_DISCARDSAMPLES 0
+#endif
+
+// The default maximum velocity of a flick.
+#ifndef QML_FLICK_DEFAULTMAXVELOCITY
+#define QML_FLICK_DEFAULTMAXVELOCITY 2500
+#endif
+
+
QT_BEGIN_NAMESPACE
+const qreal MinimumFlickVelocity = 75.0;
+
inline qreal qmlMod(qreal x, qreal y)
{
#ifdef QT_USE_MATH_H_FLOATS
return fmod(x, y);
}
-static QDeclarativeOpenMetaObjectType *qPathViewAttachedType = 0;
+static QQmlOpenMetaObjectType *qPathViewAttachedType = 0;
QQuickPathViewAttached::QQuickPathViewAttached(QObject *parent)
: QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
{
if (qPathViewAttachedType) {
- m_metaobject = new QDeclarativeOpenMetaObject(this, qPathViewAttachedType);
+ m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
m_metaobject->setCached(true);
} else {
- m_metaobject = new QDeclarativeOpenMetaObject(this);
+ m_metaobject = new QQmlOpenMetaObject(this);
}
}
m_metaobject->setValue(name, val);
}
+QQuickPathViewPrivate::QQuickPathViewPrivate()
+ : path(0), currentIndex(0), currentItemOffset(0.0), startPc(0)
+ , offset(0.0), offsetAdj(0.0), mappedRange(1.0)
+ , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
+ , autoHighlight(true), highlightUp(false), layoutScheduled(false)
+ , moving(false), flicking(false), dragging(false), requestedOnPath(false), inRequest(false)
+ , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY)
+ , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
+ , firstIndex(-1), pathItems(-1), requestedIndex(-1), requestedZ(0)
+ , moveReason(Other), moveDirection(Shortest), attType(0), highlightComponent(0), highlightItem(0)
+ , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
+ , highlightPosition(0)
+ , highlightRangeStart(0), highlightRangeEnd(0)
+ , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
+ , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
+{
+}
void QQuickPathViewPrivate::init()
{
q->setAcceptedMouseButtons(Qt::LeftButton);
q->setFlag(QQuickItem::ItemIsFocusScope);
q->setFiltersChildMouseEvents(true);
- FAST_CONNECT(&tl, SIGNAL(updated()), q, SLOT(ticked()))
- lastPosTime.invalidate();
- FAST_CONNECT(&tl, SIGNAL(completed()), q, SLOT(movementEnding()))
+ 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 onPath)
inRequest = true;
QQuickItem *item = model->item(modelIndex, false);
if (item) {
- QDeclarative_setParent_noEvent(item, q);
item->setParentItem(q);
requestedIndex = -1;
qPathViewAttachedType = attType;
att->setOnPath(false);
}
item->setParentItem(this);
- QDeclarative_setParent_noEvent(item, this);
d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
} else {
d->requestedIndex = -1;
return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
}
-QDeclarativeOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
+QQmlOpenMetaObjectType *QQuickPathViewPrivate::attachedType()
{
Q_Q(QQuickPathView);
if (!attType) {
// pre-create one metatype to share with all attached objects
- attType = new QDeclarativeOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
+ attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject, qmlEngine(q));
foreach (const QString &attr, path->attributes())
attType->createProperty(attr.toUtf8());
}
if (model && index >= 0 && index < modelCount) {
qreal start = 0.0;
- if (haveHighlightRange && highlightRangeMode != QQuickPathView::NoHighlightRange)
+ if (haveHighlightRange && (highlightRangeMode != QQuickPathView::NoHighlightRange
+ || snapMode != QQuickPathView::NoSnap))
start = highlightRangeStart;
qreal globalPos = index + offset;
globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
QQuickItem *item = 0;
if (highlightComponent) {
- QDeclarativeContext *creationContext = highlightComponent->creationContext();
- QDeclarativeContext *highlightContext = new QDeclarativeContext(
+ QQmlContext *creationContext = highlightComponent->creationContext();
+ QQmlContext *highlightContext = new QQmlContext(
creationContext ? creationContext : qmlContext(q));
QObject *nobj = highlightComponent->create(highlightContext);
if (nobj) {
- QDeclarative_setParent_noEvent(highlightContext, nobj);
+ QQml_setParent_noEvent(highlightContext, nobj);
item = qobject_cast<QQuickItem *>(nobj);
if (!item)
delete nobj;
item = new QQuickItem;
}
if (item) {
- QDeclarative_setParent_noEvent(item, q);
+ QQml_setParent_noEvent(item, q);
item->setParentItem(q);
highlightItem = item;
changed = true;
att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
}
QPointF pf = path->pointAt(percent);
- item->setX(qRound(pf.x() - item->width()/2));
- item->setY(qRound(pf.y() - item->height()/2));
+ item->setX(pf.x() - item->width()/2);
+ item->setY(pf.y() - item->height()/2);
}
void QQuickPathViewPrivate::regenerate()
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();
+}
+
/*!
\qmlclass PathView QQuickPathView
\inqmlmodule QtQuick 2
- \ingroup qml-view-elements
- \brief The PathView element lays out model-provided items on a path.
+ \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 elements like ListModel
+ 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.
For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
- \snippet doc/src/snippets/declarative/pathview/ContactModel.qml 0
+ \snippet qml/pathview/ContactModel.qml 0
This data can be represented as a PathView, like this:
- \snippet doc/src/snippets/declarative/pathview/pathview.qml 0
+ \snippet qml/pathview/pathview.qml 0
\image pathview.gif
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 doc/src/snippets/declarative/pathview/pathview.qml 1
+ \snippet qml/pathview/pathview.qml 1
- \bold Note that views do not enable \e clip automatically. If the view
+ \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, {declarative/modelviews/pathview}{PathView example}
+ \sa Path, {quick/modelviews/pathview}{PathView example}
*/
QQuickPathView::QQuickPathView(QQuickItem *parent)
This property may be used to adjust the appearance of the current item.
- \snippet doc/src/snippets/declarative/pathview/pathview.qml 1
+ \snippet qml/pathview/pathview.qml 1
*/
/*!
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 element.
+ Models can also be created directly in QML, using the ListModel type.
\sa {qmlmodels}{Data Models}
*/
return;
if (d->model) {
- disconnect(d->model, SIGNAL(modelUpdated(QDeclarativeChangeSet,bool)),
- this, SLOT(modelUpdated(QDeclarativeChangeSet,bool)));
- disconnect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
- disconnect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
+ qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
+ this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
+ qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
+ this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
+ qmlobject_disconnect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
+ this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
for (int i=0; i<d->items.count(); i++){
QQuickItem *p = d->items[i];
d->releaseItem(p);
}
d->modelCount = 0;
if (d->model) {
- connect(d->model, SIGNAL(modelUpdated(QDeclarativeChangeSet,bool)),
- this, SLOT(modelUpdated(QDeclarativeChangeSet,bool)));
- connect(d->model, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(createdItem(int,QQuickItem*)));
- connect(d->model, SIGNAL(initItem(int,QQuickItem*)), this, SLOT(initItem(int,QQuickItem*)));
+ qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)),
+ this, QQuickPathView, SLOT(modelUpdated(QQuickChangeSet,bool)));
+ qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(createdItem(int,QQuickItem*)),
+ this, QQuickPathView, SLOT(createdItem(int,QQuickItem*)));
+ qmlobject_connect(d->model, QQuickVisualModel, SIGNAL(initItem(int,QQuickItem*)),
+ this, QQuickPathView, SLOT(initItem(int,QQuickItem*)));
d->modelCount = d->model->count();
if (d->model->count())
d->offset = qmlMod(d->offset, qreal(d->model->count()));
This property holds the path used to lay out the items.
For more information see the \l Path documentation.
*/
-QDeclarativePath *QQuickPathView::path() const
+QQuickPath *QQuickPathView::path() const
{
Q_D(const QQuickPathView);
return d->path;
}
-void QQuickPathView::setPath(QDeclarativePath *path)
+void QQuickPathView::setPath(QQuickPath *path)
{
Q_D(QQuickPathView);
if (d->path == path)
return;
if (d->path)
- disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
+ qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
+ this, QQuickPathView, SLOT(pathUpdated()));
d->path = path;
- connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
+ qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
+ this, QQuickPathView, SLOT(pathUpdated()));
if (d->isValid() && isComponentComplete()) {
d->clear();
if (d->attType) {
void QQuickPathView::setCurrentIndex(int idx)
{
Q_D(QQuickPathView);
- if (d->model && d->modelCount)
- idx = qAbs(idx % d->modelCount);
+ 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))
if (d->modelCount) {
d->createCurrentItem();
if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
- d->snapToCurrent();
+ d->snapToIndex(d->currentIndex);
d->currentItemOffset = d->positionOfIndex(d->currentIndex);
d->updateHighlight();
}
Increments the current index.
- \bold Note: methods should only be called after the Component has completed.
+ \b Note: methods should only be called after the Component has completed.
*/
void QQuickPathView::incrementCurrentIndex()
{
Decrements the current index.
- \bold Note: methods should only be called after the Component has completed.
+ \b Note: methods should only be called after the Component has completed.
*/
void QQuickPathView::decrementCurrentIndex()
{
Q_D(QQuickPathView);
- if (d->model && d->modelCount) {
- int idx = currentIndex()-1;
- if (idx < 0)
- idx = d->modelCount - 1;
- d->moveDirection = QQuickPathViewPrivate::Negative;
- setCurrentIndex(idx);
- }
+ d->moveDirection = QQuickPathViewPrivate::Negative;
+ setCurrentIndex(currentIndex()-1);
}
/*!
void QQuickPathView::setOffset(qreal offset)
{
Q_D(QQuickPathView);
+ d->moveReason = QQuickPathViewPrivate::Other;
d->setOffset(offset);
d->updateCurrent();
}
\sa highlightItem, highlightRangeMode
*/
-QDeclarativeComponent *QQuickPathView::highlight() const
+QQmlComponent *QQuickPathView::highlight() const
{
Q_D(const QQuickPathView);
return d->highlightComponent;
}
-void QQuickPathView::setHighlight(QDeclarativeComponent *highlight)
+void QQuickPathView::setHighlight(QQmlComponent *highlight)
{
Q_D(QQuickPathView);
if (highlight != d->highlightComponent) {
if (d->highlightRangeStart == start || start < 0 || start > 1.0)
return;
d->highlightRangeStart = start;
- d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+ d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
refill();
emit preferredHighlightBeginChanged();
}
if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
return;
d->highlightRangeEnd = end;
- d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+ d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
refill();
emit preferredHighlightEndChanged();
}
if (d->highlightRangeMode == mode)
return;
d->highlightRangeMode = mode;
- d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+ d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
if (d->haveHighlightRange) {
d->regenerate();
- d->snapToCurrent();
+ int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
+ if (index >= 0)
+ d->snapToIndex(index);
}
emit highlightRangeModeChanged();
}
}
/*!
+ \qmlproperty real QtQuick2::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 (vel == d->maximumFlickVelocity)
+ return;
+ d->maximumFlickVelocity = vel;
+ emit maximumFlickVelocityChanged();
+}
+
+
+/*!
\qmlproperty bool QtQuick2::PathView::interactive
A user cannot drag or flick a PathView that is not interactive.
}
/*!
+ \qmlproperty bool QtQuick2::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 QtQuick2::PathView::onMovementStarted()
This handler is called when the view begins moving due to user
*/
/*!
+ \qmlsignal QtQuick2::PathView::onDragStarted()
+
+ This handler is called when the view starts to be dragged due to user
+ interaction.
+*/
+
+/*!
+ \qmlsignal QtQuick2::PathView::onDragEnded()
+
+ This handler is called 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.
+*/
+
+/*!
\qmlproperty Component QtQuick2::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 {qmlmodels}{Data Model}.
- The number of elements in the delegate has a direct effect on the
+ 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 elements when needed.
+ 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 doc/src/snippets/declarative/pathview/pathview.qml 1
+ \snippet qml/pathview/pathview.qml 1
*/
-QDeclarativeComponent *QQuickPathView::delegate() const
+QQmlComponent *QQuickPathView::delegate() const
{
Q_D(const QQuickPathView);
if (d->model) {
return 0;
}
-void QQuickPathView::setDelegate(QDeclarativeComponent *delegate)
+void QQuickPathView::setDelegate(QQmlComponent *delegate)
{
Q_D(QQuickPathView);
if (delegate == this->delegate())
emit pathItemCountChanged();
}
+/*!
+ \qmlproperty enumeration QtQuick2::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();
+}
+
QPointF QQuickPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
{
- //XXX maybe do recursively at increasing resolution.
+ qreal samples = qMin(path->path().length()/5, qreal(500.0));
+ qreal res = path->path().length()/samples;
+
qreal mindist = 1e10; // big number
QPointF nearPoint = path->pointAt(0);
qreal nearPc = 0;
- for (qreal i=1; i < 1000; i++) {
- QPointF pt = path->pointAt(i/1000.0);
+
+ // get rough pos
+ for (qreal i=1; i < samples; i++) {
+ QPointF pt = path->pointAt(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.0; i < approxPc+1.0; i += 1/(2*res)) {
+ QPointF pt = path->pointAt(i/samples);
QPointF diff = pt - point;
qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
if (dist < mindist) {
}
if (nearPercent)
- *nearPercent = nearPc / 1000.0;
+ *nearPercent = nearPc / samples;
return nearPoint;
}
+void QQuickPathViewPrivate::addVelocitySample(qreal v)
+{
+ velocityBuffer.append(v);
+ if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
+ velocityBuffer.remove(0);
+}
+
+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;
+ }
+ return velocity;
+}
+
+qint64 QQuickPathViewPrivate::computeCurrentTime(QInputEvent *event)
+{
+ if (0 != event->timestamp() && QQuickItemPrivate::consistentTime == -1) {
+ return event->timestamp();
+ }
+
+ return QQuickItemPrivate::elapsed(timer);
+}
+
void QQuickPathView::mousePressEvent(QMouseEvent *event)
{
Q_D(QQuickPathView);
void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
{
- Q_Q(QQuickPathView);
- if (!interactive || !items.count())
+ if (!interactive || !items.count() || !model || !modelCount)
return;
- QPointF scenePoint = q->mapToScene(event->localPos());
+ velocityBuffer.clear();
int idx = 0;
for (; idx < items.count(); ++idx) {
- QRectF rect = items.at(idx)->boundingRect();
- rect = items.at(idx)->mapRectToScene(rect);
- if (rect.contains(scenePoint))
+ QQuickItem *item = items.at(idx);
+ if (item->contains(item->mapFromScene(event->windowPos())))
break;
}
if (idx == items.count() && dragMargin == 0.) // didn't click on an item
return;
}
- if (tl.isActive() && flicking)
+
+ if (tl.isActive() && flicking && flickDuration && qreal(tl.time())/flickDuration < 0.8)
stealMouse = true; // If we've been flicked then steal the click.
else
stealMouse = false;
- lastElapsed = 0;
- lastDist = 0;
- QQuickItemPrivate::start(lastPosTime);
+ QQuickItemPrivate::start(timer);
+ lastPosTime = computeCurrentTime(event);
tl.clear();
}
void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event)
{
Q_Q(QQuickPathView);
- if (!interactive || !lastPosTime.isValid())
+ if (!interactive || !timer.isValid() || !model || !modelCount)
return;
+ qint64 currentTimestamp = computeCurrentTime(event);
qreal newPc;
QPointF pathPoint = pointNear(event->localPos(), &newPc);
if (!stealMouse) {
QPointF delta = pathPoint - startPoint;
if (qAbs(delta.x()) > qApp->styleHints()->startDragDistance() || qAbs(delta.y()) > qApp->styleHints()->startDragDistance()) {
stealMouse = true;
- startPc = newPc;
}
- }
-
- if (stealMouse) {
+ } else {
moveReason = QQuickPathViewPrivate::Mouse;
qreal diff = (newPc - startPc)*modelCount*mappedRange;
if (diff) {
else if (diff < -modelCount/2)
diff += modelCount;
- lastElapsed = QQuickItemPrivate::restart(lastPosTime);
- lastDist = diff;
- startPc = newPc;
+ 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_Q(QQuickPathView);
stealMouse = false;
q->setKeepMouseGrab(false);
- if (!interactive || !lastPosTime.isValid())
+ setDragging(false);
+ if (!interactive || !timer.isValid() || !model || !modelCount) {
+ timer.invalidate();
+ if (!tl.isActive())
+ q->movementEnding();
return;
+ }
- qreal elapsed = qreal(lastElapsed + QQuickItemPrivate::elapsed(lastPosTime)) / 1000.;
- qreal velocity = elapsed > 0. ? lastDist / elapsed : 0;
- if (model && modelCount && qAbs(velocity) > 1.) {
- qreal count = pathItems == -1 ? modelCount : pathItems;
- if (qAbs(velocity) > count * 2) // limit velocity
- velocity = (velocity > 0 ? count : -count) * 2;
+ qreal velocity = calcVelocity();
+ qreal count = modelCount*mappedRange;
+ qreal pixelVelocity = (path->path().length()/count) * velocity;
+ if (qAbs(pixelVelocity) > MinimumFlickVelocity) {
+ if (qAbs(pixelVelocity) > maximumFlickVelocity || snapMode == QQuickPathView::SnapOneItem) {
+ // limit velocity
+ qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
+ velocity = maxVel / (path->path().length()/count);
+ }
// Calculate the distance to be travelled
qreal v2 = velocity*velocity;
qreal accel = deceleration/10;
- // + 0.25 to encourage moving at least one item in the flick direction
- qreal dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
- if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
- // round to nearest item.
- if (velocity > 0.)
- dist = qRound(dist + offset) - offset;
- else
- dist = qRound(dist - offset) + offset;
+ 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) + 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.;
} else {
accel = v2 / (2.0f * qAbs(dist));
}
+ } else {
+ dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0)));
}
+ flickDuration = static_cast<int>(1000 * qAbs(velocity) / accel);
offsetAdj = 0.0;
moveOffset.setValue(offset);
tl.accel(moveOffset, velocity, accel, dist);
- tl.callback(QDeclarativeTimeLineCallback(&moveOffset, fixOffsetCallback, this));
+ tl.callback(QQuickTimeLineCallback(&moveOffset, fixOffsetCallback, this));
if (!flicking) {
flicking = true;
emit q->flickingChanged();
fixOffset();
}
- lastPosTime.invalidate();
+ timer.invalidate();
if (!tl.isActive())
q->movementEnding();
}
bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
{
Q_D(QQuickPathView);
- QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
- QQuickCanvas *c = canvas();
+ QPointF localPos = mapFromScene(event->windowPos());
+
+ QQuickWindow *c = window();
QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
bool stealThisEvent = d->stealMouse;
- if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
- QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+ if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
+ QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
event->button(), event->buttons(), event->modifiers());
mouseEvent.setAccepted(false);
grabMouse();
return d->stealMouse;
- } else if (d->lastPosTime.isValid()) {
- d->lastPosTime.invalidate();
+ } else if (d->timer.isValid()) {
+ d->timer.invalidate();
d->fixOffset();
}
if (event->type() == QEvent::MouseButtonRelease)
// fix our state
d->stealMouse = false;
setKeepMouseGrab(false);
- d->lastPosTime.invalidate();
+ d->timer.invalidate();
+ d->fixOffset();
+ d->setDragging(false);
+ if (!d->tl.isActive())
+ movementEnding();
}
}
d->regenerate();
}
d->updateHighlight();
+ d->updateCurrent();
if (d->modelCount)
emit countChanged();
void QQuickPathView::refill()
{
Q_D(QQuickPathView);
+
+ d->layoutScheduled = false;
+
if (!d->isValid() || !isComponentComplete())
return;
- d->layoutScheduled = false;
bool currentVisible = false;
// first move existing items and remove items off path
if (d->items.count() < count) {
int idx = qRound(d->modelCount - d->offset) % d->modelCount;
qreal startPos = 0.0;
- if (d->haveHighlightRange && d->highlightRangeMode != QQuickPathView::NoHighlightRange)
+ if (d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
+ || d->snapMode != QQuickPathView::NoSnap))
startPos = d->highlightRangeStart;
if (d->firstIndex >= 0) {
startPos = d->positionOfIndex(d->firstIndex);
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setOnPath(false);
} else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
- if (d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false)) {
+ if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, false))) {
d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0);
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setIsCurrentItem(true);
}
}
} else if (!waiting && !d->currentItem) {
- if (d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true)) {
+ if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex, true))) {
d->currentItem->setFocus(true);
if (QQuickPathViewAttached *att = d->attached(d->currentItem))
att->setIsCurrentItem(true);
d->releaseItem(d->itemCache.takeLast());
}
-void QQuickPathView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool reset)
+void QQuickPathView::modelUpdated(const QQuickChangeSet &changeSet, bool reset)
{
Q_D(QQuickPathView);
if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
int moveOffset;
bool currentChanged = false;
bool changedOffset = false;
- foreach (const QDeclarativeChangeSet::Remove &r, changeSet.removes()) {
+ foreach (const QQuickChangeSet::Remove &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.
- d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
if (r.isMove()) {
moveId = r.moveId;
moveOffset = d->currentIndex - r.index;
d->releaseItem(d->currentItem);
d->currentItem = 0;
}
+ d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
currentChanged = true;
}
}
d->modelCount -= r.count;
}
- foreach (const QDeclarativeChangeSet::Insert &i, changeSet.inserts()) {
+ foreach (const QQuickChangeSet::Insert &i, changeSet.inserts()) {
if (d->modelCount) {
if (moveId == -1 && i.index <= d->currentIndex) {
d->currentIndex += i.count;
d->offset = qmlMod(d->offset, 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();
// find the item closest to the snap position
int QQuickPathViewPrivate::calcCurrentIndex()
{
- int current = -1;
+ int current = 0;
if (modelCount && model && items.count()) {
offset = qmlMod(offset, modelCount);
if (offset < 0)
return;
int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
if (itemIndex < items.count()) {
- if (currentItem = getItem(currentIndex, currentIndex, true)) {
+ if ((currentItem = getItem(currentIndex, currentIndex, true))) {
currentItem->setFocus(true);
if (QQuickPathViewAttached *att = attached(currentItem))
att->setIsCurrentItem(true);
}
} else if (currentIndex >= 0 && currentIndex < modelCount) {
- if (currentItem = getItem(currentIndex, currentIndex, false)) {
+ if ((currentItem = getItem(currentIndex, currentIndex, false))) {
updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0);
if (QQuickPathViewAttached *att = attached(currentItem))
att->setIsCurrentItem(true);
void QQuickPathViewPrivate::updateCurrent()
{
Q_Q(QQuickPathView);
- if (moveReason != Mouse)
+ if (moveReason == SetIndex)
return;
if (!modelCount || !haveHighlightRange || highlightRangeMode != QQuickPathView::StrictlyEnforceRange)
return;
{
Q_Q(QQuickPathView);
if (model && items.count()) {
- if (haveHighlightRange && highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
+ if (haveHighlightRange && (highlightRangeMode == QQuickPathView::StrictlyEnforceRange
+ || snapMode != QQuickPathView::NoSnap)) {
int curr = calcCurrentIndex();
- if (curr != currentIndex)
+ if (curr != currentIndex && highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
q->setCurrentIndex(curr);
else
- snapToCurrent();
+ snapToIndex(curr);
}
}
}
-void QQuickPathViewPrivate::snapToCurrent()
+void QQuickPathViewPrivate::snapToIndex(int index)
{
if (!model || modelCount <= 0)
return;
- qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
+ qreal targetOffset = qmlMod(modelCount - index, modelCount);
if (offset == targetOffset)
return;
if (!duration) {
tl.set(moveOffset, targetOffset);
- } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
+ } else if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2.0)) {
qreal distance = modelCount - targetOffset + offset;
if (targetOffset > moveOffset) {
tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
} else {
tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
}
- } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
+ } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2.0) {
qreal distance = modelCount - offset + targetOffset;
if (targetOffset < moveOffset) {
tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));