From a47c6ddde353b1ffe8fd4037817143ff9f338654 Mon Sep 17 00:00:00 2001 From: Martin Jones Date: Fri, 27 Nov 2009 14:26:49 +1000 Subject: Add GridView.positionViewAtIndex(). --- .../graphicsitems/qmlgraphicsgridview.cpp | 41 ++++++++-- .../graphicsitems/qmlgraphicsgridview_p.h | 1 + .../tst_qmlgraphicsgridview.cpp | 91 ++++++++++++++++++++++ 3 files changed, 127 insertions(+), 6 deletions(-) diff --git a/src/declarative/graphicsitems/qmlgraphicsgridview.cpp b/src/declarative/graphicsitems/qmlgraphicsgridview.cpp index e36ea50..81128e7 100644 --- a/src/declarative/graphicsitems/qmlgraphicsgridview.cpp +++ b/src/declarative/graphicsitems/qmlgraphicsgridview.cpp @@ -388,9 +388,9 @@ void QmlGraphicsGridViewPrivate::refill(qreal from, qreal to) to += buffer; bool changed = false; - int colPos = 0; - int rowPos = 0; - int modelIndex = 0; + int colPos = colPosAt(visibleIndex); + int rowPos = rowPosAt(visibleIndex); + int modelIndex = visibleIndex; if (visibleItems.count()) { rowPos = visibleItems.last()->rowPos(); colPos = visibleItems.last()->colPos() + colSize(); @@ -822,14 +822,14 @@ void QmlGraphicsGridView::setModel(const QVariant &model) } /*! - \qmlproperty component GridView::delegate + \qmlproperty component GridView::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}. - Here is an example delegate: - \snippet doc/src/snippets/declarative/gridview/gridview.qml 0 + Here is an example delegate: + \snippet doc/src/snippets/declarative/gridview/gridview.qml 0 */ QmlComponent *QmlGraphicsGridView::delegate() const { @@ -1292,6 +1292,35 @@ void QmlGraphicsGridView::moveCurrentIndexRight() } } +void QmlGraphicsGridView::positionViewAtIndex(int index) +{ + Q_D(QmlGraphicsGridView); + if (!d->isValid() || index < 0 || index >= d->model->count()) + return; + + qreal maxExtent = d->flow == QmlGraphicsGridView::LeftToRight ? -maxYExtent() : -maxXExtent(); + FxGridItem *item = d->visibleItem(index); + if (item) { + // Already created - just move to top of view + int pos = qMin(item->rowPos(), maxExtent); + d->setPosition(pos); + } else { + int pos = d->rowPosAt(index); + // save the currently visible items in case any of them end up visible again + QList oldVisible = d->visibleItems; + d->visibleItems.clear(); + d->visibleIndex = index - index % d->columns; + d->setPosition(pos); + // setPosition() will cause refill. Adjust if we have moved beyond range + if (d->position() > maxExtent) + d->setPosition(maxExtent); + // now release the reference to all the old visible items. + for (int i = 0; i < oldVisible.count(); ++i) + d->releaseItem(oldVisible.at(i)); + } +} + + void QmlGraphicsGridView::componentComplete() { Q_D(QmlGraphicsGridView); diff --git a/src/declarative/graphicsitems/qmlgraphicsgridview_p.h b/src/declarative/graphicsitems/qmlgraphicsgridview_p.h index 3e09cf3..99515a3 100644 --- a/src/declarative/graphicsitems/qmlgraphicsgridview_p.h +++ b/src/declarative/graphicsitems/qmlgraphicsgridview_p.h @@ -121,6 +121,7 @@ public Q_SLOTS: void moveCurrentIndexDown(); void moveCurrentIndexLeft(); void moveCurrentIndexRight(); + void positionViewAtIndex(int index); Q_SIGNALS: void countChanged(); diff --git a/tests/auto/declarative/qmlgraphicsgridview/tst_qmlgraphicsgridview.cpp b/tests/auto/declarative/qmlgraphicsgridview/tst_qmlgraphicsgridview.cpp index f31ea49..96a164b 100644 --- a/tests/auto/declarative/qmlgraphicsgridview/tst_qmlgraphicsgridview.cpp +++ b/tests/auto/declarative/qmlgraphicsgridview/tst_qmlgraphicsgridview.cpp @@ -65,6 +65,7 @@ private slots: void currentIndex(); void defaultValues(); void properties(); + void positionViewAtIndex(); private: QmlView *createView(const QString &filename); @@ -809,6 +810,96 @@ void tst_QmlGraphicsGridView::properties() delete obj; } +void tst_QmlGraphicsGridView::positionViewAtIndex() +{ + QmlView *canvas = createView(SRCDIR "/data/gridview.qml"); + + TestModel model; + for (int i = 0; i < 40; i++) + model.addItem("Item" + QString::number(i), ""); + + QmlContext *ctxt = canvas->rootContext(); + ctxt->setContextProperty("testModel", &model); + ctxt->setContextProperty("testTopToBottom", QVariant(false)); + + canvas->execute(); + qApp->processEvents(); + + QmlGraphicsGridView *gridview = findItem(canvas->root(), "grid"); + QVERIFY(gridview != 0); + + QmlGraphicsItem *viewport = gridview->viewport(); + QVERIFY(viewport != 0); + + // Confirm items positioned correctly + int itemCount = findItems(viewport, "wrapper").count(); + for (int i = 0; i < model.count() && i < itemCount-1; ++i) { + QmlGraphicsItem *item = findItem(viewport, "wrapper", i); + if (!item) qWarning() << "Item" << i << "not found"; + QVERIFY(item); + QCOMPARE(item->x(), (i%3)*80.); + QCOMPARE(item->y(), (i/3)*60.); + } + + // Position on a currently visible item + gridview->positionViewAtIndex(4); + QCOMPARE(gridview->viewportY(), 60.); + + // Confirm items positioned correctly + itemCount = findItems(viewport, "wrapper").count(); + for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) { + QmlGraphicsItem *item = findItem(viewport, "wrapper", i); + if (!item) qWarning() << "Item" << i << "not found"; + QVERIFY(item); + QCOMPARE(item->x(), (i%3)*80.); + QCOMPARE(item->y(), (i/3)*60.); + } + + // Position on an item beyond the visible items + gridview->positionViewAtIndex(21); + QCOMPARE(gridview->viewportY(), 420.); + + // Confirm items positioned correctly + itemCount = findItems(viewport, "wrapper").count(); + for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) { + QmlGraphicsItem *item = findItem(viewport, "wrapper", i); + if (!item) qWarning() << "Item" << i << "not found"; + QVERIFY(item); + QCOMPARE(item->x(), (i%3)*80.); + QCOMPARE(item->y(), (i/3)*60.); + } + + // Position on an item that would leave empty space if positioned at the top + gridview->positionViewAtIndex(31); + QCOMPARE(gridview->viewportY(), 520.); + + // Confirm items positioned correctly + itemCount = findItems(viewport, "wrapper").count(); + for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) { + QmlGraphicsItem *item = findItem(viewport, "wrapper", i); + if (!item) qWarning() << "Item" << i << "not found"; + QVERIFY(item); + QCOMPARE(item->x(), (i%3)*80.); + QCOMPARE(item->y(), (i/3)*60.); + } + + // Position at the beginning again + gridview->positionViewAtIndex(0); + QCOMPARE(gridview->viewportY(), 0.); + + // Confirm items positioned correctly + itemCount = findItems(viewport, "wrapper").count(); + for (int i = 0; i < model.count() && i < itemCount-1; ++i) { + QmlGraphicsItem *item = findItem(viewport, "wrapper", i); + if (!item) qWarning() << "Item" << i << "not found"; + QVERIFY(item); + QCOMPARE(item->x(), (i%3)*80.); + QCOMPARE(item->y(), (i/3)*60.); + } + + delete canvas; +} + QmlView *tst_QmlGraphicsGridView::createView(const QString &filename) { QmlView *canvas = new QmlView(0); -- cgit v0.12 From 68e3cab8a8183a5a88e5be092471a05692e05afe Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Mon, 30 Nov 2009 11:29:39 +1000 Subject: Support disabling a Behavior. --- src/declarative/util/qmlbehavior.cpp | 34 ++++++++++++++++++++-- src/declarative/util/qmlbehavior_p.h | 7 +++++ tests/auto/declarative/behaviors/data/disabled.qml | 27 +++++++++++++++++ tests/auto/declarative/behaviors/tst_behaviors.cpp | 15 ++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 tests/auto/declarative/behaviors/data/disabled.qml diff --git a/src/declarative/util/qmlbehavior.cpp b/src/declarative/util/qmlbehavior.cpp index 711c70d..23d3838 100644 --- a/src/declarative/util/qmlbehavior.cpp +++ b/src/declarative/util/qmlbehavior.cpp @@ -55,18 +55,21 @@ class QmlBehaviorPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QmlBehavior) public: - QmlBehaviorPrivate() : animation(0) {} + QmlBehaviorPrivate() : animation(0), enabled(true) {} QmlMetaProperty property; QVariant currentValue; QmlAbstractAnimation *animation; + bool enabled; }; /*! \qmlclass Behavior QmlBehavior \brief The Behavior element allows you to specify a default animation for a property change. - In example below, the rect will use a bounce easing curve over 200 millisecond for any changes to its y property: + Behaviors provide one way to specify \l{qmlanimation.html}{animations} in QML. + + In the example below, the rect will use a bounce easing curve over 200 millisecond for any changes to its y property: \code Rectangle { width: 20; height: 20 @@ -80,6 +83,9 @@ public: } } \endcode + + Currently only a single Behavior may be specified for a property; + this Behavior can be enabled and disabled via the \l{enabled} property. */ @@ -118,10 +124,32 @@ void QmlBehavior::setAnimation(QmlAbstractAnimation *animation) d->animation->setTarget(d->property); } +/*! + \qmlproperty bool Behavior::enabled + Whether the Behavior will be triggered when the property it is tracking changes. + + By default a Behavior is enabled. +*/ + +bool QmlBehavior::enabled() const +{ + Q_D(const QmlBehavior); + return d->enabled; +} + +void QmlBehavior::setEnabled(bool enabled) +{ + Q_D(QmlBehavior); + if (d->enabled == enabled) + return; + d->enabled = enabled; + emit enabledChanged(); +} + void QmlBehavior::write(const QVariant &value) { Q_D(QmlBehavior); - if (!d->animation) { + if (!d->animation || !d->enabled) { d->property.write(value, QmlMetaProperty::BypassInterceptor | QmlMetaProperty::DontRemoveBinding); return; } diff --git a/src/declarative/util/qmlbehavior_p.h b/src/declarative/util/qmlbehavior_p.h index b61df32..581a0a8 100644 --- a/src/declarative/util/qmlbehavior_p.h +++ b/src/declarative/util/qmlbehavior_p.h @@ -63,6 +63,7 @@ class Q_DECLARATIVE_EXPORT QmlBehavior : public QObject, public QmlPropertyValue Q_INTERFACES(QmlPropertyValueInterceptor) Q_CLASSINFO("DefaultProperty", "animation") Q_PROPERTY(QmlAbstractAnimation *animation READ animation WRITE setAnimation) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) public: QmlBehavior(QObject *parent=0); @@ -73,6 +74,12 @@ public: QmlAbstractAnimation *animation(); void setAnimation(QmlAbstractAnimation *); + + bool enabled() const; + void setEnabled(bool enabled); + +Q_SIGNALS: + void enabledChanged(); }; QT_END_NAMESPACE diff --git a/tests/auto/declarative/behaviors/data/disabled.qml b/tests/auto/declarative/behaviors/data/disabled.qml new file mode 100644 index 0000000..e7b8d51 --- /dev/null +++ b/tests/auto/declarative/behaviors/data/disabled.qml @@ -0,0 +1,27 @@ +import Qt 4.6 +Rectangle { + width: 400 + height: 400 + Rectangle { + id: rect + objectName: "MyRect" + width: 100; height: 100; color: "green" + x: Behavior { + objectName: "MyBehavior"; + enabled: false + NumberAnimation { duration: 200; } + } + } + MouseRegion { + id: clicker + anchors.fill: parent + } + states: State { + name: "moved" + when: clicker.pressed + PropertyChanges { + target: rect + x: 200 + } + } +} diff --git a/tests/auto/declarative/behaviors/tst_behaviors.cpp b/tests/auto/declarative/behaviors/tst_behaviors.cpp index 6343968..e1376ce 100644 --- a/tests/auto/declarative/behaviors/tst_behaviors.cpp +++ b/tests/auto/declarative/behaviors/tst_behaviors.cpp @@ -64,6 +64,7 @@ private slots: void emptyBehavior(); void nonSelectingBehavior(); void reassignedAnimation(); + void disabled(); }; void tst_behaviors::simpleBehavior() @@ -243,6 +244,20 @@ void tst_behaviors::reassignedAnimation() rect->findChild("MyBehavior"))->animation())->duration(), 200); } +void tst_behaviors::disabled() +{ + QmlEngine engine; + QmlComponent c(&engine, QUrl("file://" SRCDIR "/data/disabled.qml")); + QmlGraphicsRectangle *rect = qobject_cast(c.create()); + QVERIFY(rect); + QCOMPARE(rect->findChild("MyBehavior")->enabled(), false); + + rect->setState("moved"); + qreal x = qobject_cast(rect->findChild("MyRect"))->x(); + QCOMPARE(x, qreal(200)); //should change immediately + +} + QTEST_MAIN(tst_behaviors) #include "tst_behaviors.moc" -- cgit v0.12 From 541c254ce91ea7dea01081f0b093b902527c3476 Mon Sep 17 00:00:00 2001 From: Martin Jones Date: Mon, 30 Nov 2009 15:50:28 +1000 Subject: Add ListView snapMode to ensure flicking settles on an item boundary. --- examples/declarative/parallax/qml/ParallaxView.qml | 8 +- .../graphicsitems/qmlgraphicsflickable.cpp | 24 -- .../graphicsitems/qmlgraphicsflickable_p_p.h | 4 +- .../graphicsitems/qmlgraphicslistview.cpp | 351 +++++++++++++++++---- .../graphicsitems/qmlgraphicslistview_p.h | 7 + 5 files changed, 298 insertions(+), 96 deletions(-) diff --git a/examples/declarative/parallax/qml/ParallaxView.qml b/examples/declarative/parallax/qml/ParallaxView.qml index 98dd246..ff4a85a 100644 --- a/examples/declarative/parallax/qml/ParallaxView.qml +++ b/examples/declarative/parallax/qml/ParallaxView.qml @@ -25,13 +25,7 @@ Item { anchors.fill: parent model: VisualItemModel { id: visualModel } - highlight: Rectangle { height: 1; width: 1 } - highlightMoveSpeed: 2000 - preferredHighlightBegin: 0 - preferredHighlightEnd: 0 - highlightRangeMode: "StrictlyEnforceRange" - - flickDeceleration: 1000 + snapMode: ListView.SnapOneItem } ListView { diff --git a/src/declarative/graphicsitems/qmlgraphicsflickable.cpp b/src/declarative/graphicsitems/qmlgraphicsflickable.cpp index cc0f905..e010081 100644 --- a/src/declarative/graphicsitems/qmlgraphicsflickable.cpp +++ b/src/declarative/graphicsitems/qmlgraphicsflickable.cpp @@ -855,30 +855,6 @@ void QmlGraphicsFlickable::viewportMoved() QmlGraphicsItemPrivate::restart(d->velocityTime); d->lastFlickablePosition = QPointF(d->_moveY.value(), d->_moveX.value()); - if (d->flicked && d->timeline.time() > d->vTime) { - // Near an end and it seems that the extent has changed? - // Recalculate the flick so that we don't end up in an odd position. - if (d->velocityY > 0) { - const qreal minY = minYExtent(); - if (minY - d->_moveY.value() < height()/2 && minY != d->flickTargetY) - d->flickY(-d->verticalVelocity.value()); - } else if (d->velocityY < 0) { - const qreal maxY = maxYExtent(); - if (d->_moveY.value() - maxY < height()/2 && maxY != d->flickTargetY) - d->flickY(-d->verticalVelocity.value()); - } - - if (d->velocityX > 0) { - const qreal minX = minXExtent(); - if (minX - d->_moveX.value() < height()/2 && minX != d->flickTargetX) - d->flickX(-d->horizontalVelocity.value()); - } else if (d->velocityX < 0) { - const qreal maxX = maxXExtent(); - if (d->_moveX.value() - maxX < height()/2 && maxX != d->flickTargetX) - d->flickX(-d->horizontalVelocity.value()); - } - } - d->vTime = d->timeline.time(); d->updateBeginningEnd(); } diff --git a/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h b/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h index 2850e35..8044ba7 100644 --- a/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h +++ b/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h @@ -111,8 +111,8 @@ public: QTime velocityTime; QPointF lastFlickablePosition; qreal reportedVelocitySmoothing; - int flickTargetX; - int flickTargetY; + qreal flickTargetX; + qreal flickTargetY; QGraphicsSceneMouseEvent *delayedPressEvent; QGraphicsItem *delayedPressTarget; QBasicTimer delayedPressTimer; diff --git a/src/declarative/graphicsitems/qmlgraphicslistview.cpp b/src/declarative/graphicsitems/qmlgraphicslistview.cpp index 7d0b99a..9dd2ab4 100644 --- a/src/declarative/graphicsitems/qmlgraphicslistview.cpp +++ b/src/declarative/graphicsitems/qmlgraphicslistview.cpp @@ -177,8 +177,9 @@ public: , highlightComponent(0), highlight(0), trackedItem(0) , moveReason(Other), buffer(0), highlightPosAnimator(0), highlightSizeAnimator(0), spacing(0.0) , highlightMoveSpeed(400), highlightResizeSpeed(400), highlightRange(QmlGraphicsListView::NoHighlightRange) - , ownModel(false), wrap(false), autoHighlight(true) - , haveHighlightRange(false) + , snapMode(QmlGraphicsListView::NoSnap), overshootDist(0.0) + , ownModel(false), wrap(false), autoHighlight(true), haveHighlightRange(false) + , correctFlick(true) {} void init(); @@ -197,6 +198,32 @@ public: return 0; } + FxListItem *firstVisibleItem() const { + const qreal pos = position(); + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems.at(i); + if (item->index != -1 && item->endPosition() > pos) + return item; + } + return 0; + } + + FxListItem *nextVisibleItem() const { + qDebug() << "last vis"; + const qreal pos = position(); + bool foundFirst = false; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems.at(i); + if (item->index != -1) { + if (foundFirst) + return item; + else if (item->position() < pos && item->endPosition() > pos) + foundFirst = true; + } + } + return 0; + } + qreal position() const { Q_Q(const QmlGraphicsListView); return orient == QmlGraphicsListView::Vertical ? q->viewportY() : q->viewportX(); @@ -287,11 +314,42 @@ public: return index; } - //XXX Rough. Only works for fixed size items. qreal snapPosAt(qreal pos) { + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if ((item->index == model->count()-1 || itemTop >= pos-item->size()/2) + && (item->index == 0 || itemTop <= pos+item->size()/2)) + return item->position(); + } + if (visibleItems.count()) { + qreal firstPos = visibleItems.first()->position(); + qreal endPos = visibleItems.last()->position(); + if (pos < firstPos) { + return firstPos - qRound((firstPos - pos) / averageSize) * averageSize; + } else if (pos > endPos) + return endPos + qRound((pos - endPos) / averageSize) * averageSize; + } return qRound((pos - startPosition()) / averageSize) * averageSize + startPosition(); } + FxListItem *snapItemAt(qreal pos) { + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if ((item->index == model->count()-1 || itemTop >= pos-item->size()/2) + && (item->index == 0 || itemTop <= pos+item->size()/2)) + return item; + } + if (visibleItems.count() && visibleItems.first()->position() <= pos) + return visibleItems.first(); + return 0; + } + int lastVisibleIndex() const { int lastIndex = -1; for (int i = visibleItems.count()-1; i >= 0; --i) { @@ -409,11 +467,14 @@ public: qreal highlightMoveSpeed; qreal highlightResizeSpeed; QmlGraphicsListView::HighlightRangeMode highlightRange; + QmlGraphicsListView::SnapMode snapMode; + qreal overshootDist; bool ownModel : 1; bool wrap : 1; bool autoHighlight : 1; bool haveHighlightRange : 1; + bool correctFlick : 1; }; void QmlGraphicsListViewPrivate::init() @@ -809,9 +870,11 @@ void QmlGraphicsListViewPrivate::fixupPosition() void QmlGraphicsListViewPrivate::fixupY() { - QmlGraphicsFlickablePrivate::fixupY(); + Q_Q(QmlGraphicsListView); if (orient == QmlGraphicsListView::Horizontal) return; + if (!q->yflick() || _moveY.timeLine()) + return; if (haveHighlightRange && highlightRange == QmlGraphicsListView::StrictlyEnforceRange) { if (currentItem && highlight && currentItem->position() != highlight->position()) { @@ -820,14 +883,29 @@ void QmlGraphicsListViewPrivate::fixupY() timeline.move(_moveY, -(currentItem->position() - highlightRangeStart), QEasingCurve(QEasingCurve::InOutQuad), 200); vTime = timeline.time(); } + } else if (snapMode != QmlGraphicsListView::NoSnap) { + moveReason = Mouse; + if (FxListItem *item = snapItemAt(position())) { + qreal pos = qMin(item->position() - highlightRangeStart, -q->maxYExtent()); + qreal dist = qAbs(_moveY + pos); + if (dist > 0) { + timeline.reset(_moveY); + timeline.move(_moveY, -pos, QEasingCurve(QEasingCurve::InOutQuad), 200); + vTime = timeline.time(); + } + } + } else { + QmlGraphicsFlickablePrivate::fixupY(); } } void QmlGraphicsListViewPrivate::fixupX() { - QmlGraphicsFlickablePrivate::fixupX(); + Q_Q(QmlGraphicsListView); if (orient == QmlGraphicsListView::Vertical) return; + if (!q->xflick() || _moveX.timeLine()) + return; if (haveHighlightRange && highlightRange == QmlGraphicsListView::StrictlyEnforceRange) { if (currentItem && highlight && currentItem->position() != highlight->position()) { @@ -836,6 +914,19 @@ void QmlGraphicsListViewPrivate::fixupX() timeline.move(_moveX, -(currentItem->position() - highlightRangeStart), QEasingCurve(QEasingCurve::InOutQuad), 200); vTime = timeline.time(); } + } else if (snapMode != QmlGraphicsListView::NoSnap) { + moveReason = Mouse; + if (FxListItem *item = snapItemAt(position())) { + qreal pos = qMin(item->position() - highlightRangeStart, -q->maxXExtent()); + qreal dist = qAbs(_moveX + pos); + if (dist > 0) { + timeline.reset(_moveX); + timeline.move(_moveX, -pos, QEasingCurve(QEasingCurve::InOutQuad), 200); + vTime = timeline.time(); + } + } + } else { + QmlGraphicsFlickablePrivate::fixupX(); } } @@ -843,23 +934,38 @@ void QmlGraphicsListViewPrivate::flickX(qreal velocity) { Q_Q(QmlGraphicsListView); - if (!haveHighlightRange || highlightRange != QmlGraphicsListView::StrictlyEnforceRange) { + if ((!haveHighlightRange || highlightRange != QmlGraphicsListView::StrictlyEnforceRange) && snapMode == QmlGraphicsListView::NoSnap) { QmlGraphicsFlickablePrivate::flickX(velocity); return; } - qreal maxDistance = -1; + const qreal maxX = q->maxXExtent(); + const qreal minX = q->minXExtent(); // -ve velocity means list is moving up if (velocity > 0) { - if (_moveX.value() < q->minXExtent()) - maxDistance = qAbs(q->minXExtent() -_moveX.value() + (overShoot?30:0)); - flickTargetX = q->minXExtent(); + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = firstVisibleItem()) + maxDistance = qAbs(item->position() + _moveX.value()); + } else if (_moveX.value() < minX) { + maxDistance = qAbs(minX -_moveX.value() + (overShoot?30:0)); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetX = minX; } else { - if (_moveX.value() > q->maxXExtent()) - maxDistance = qAbs(q->maxXExtent() - _moveX.value()) + (overShoot?30:0); - flickTargetX = q->maxXExtent(); - } - if (maxDistance > 0) { + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = nextVisibleItem()) + maxDistance = qAbs(item->position() + _moveX.value()); + } else if (_moveX.value() > maxX) { + maxDistance = qAbs(maxX - _moveX.value()) + (overShoot?30:0); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetX = maxX; + } + if (maxDistance > 0 && (snapMode != QmlGraphicsListView::NoSnap || highlightRange == QmlGraphicsListView::StrictlyEnforceRange)) { + // These modes require the list to stop exactly on an item boundary. + // The initial flick will estimate the boundary to stop on. + // Since list items can have variable sizes, the boundary will be + // reevaluated and adjusted as we approach the boundary. qreal v = velocity; if (maxVelocity != -1 && maxVelocity < qAbs(v)) { if (v < 0) @@ -867,31 +973,57 @@ void QmlGraphicsListViewPrivate::flickX(qreal velocity) else v = maxVelocity; } - qreal accel = deceleration; - qreal v2 = v * v; - qreal maxAccel = v2 / (2.0f * maxDistance); - if (maxAccel < accel) { - // If we are not flicking to the end then attempt to stop exactly on an item boundary - qreal dist = v2 / accel / 2.0; - if (v > 0) - dist = -dist; - dist = -_moveX.value() - snapPosAt(-(_moveX.value() - highlightRangeStart) + dist) + highlightRangeStart; - if ((v < 0 && dist >= 0) || (v > 0 && dist <= 0)) { - timeline.reset(_moveX); - fixupX(); - return; - } - accel = v2 / (2.0f * qAbs(dist)); - } - timeline.reset(_moveX); - timeline.accel(_moveX, v, accel, maxDistance); - timeline.execute(fixupXEvent); if (!flicked) { + // the initial flick - estimate boundary + qreal accel = deceleration; + qreal v2 = v * v; + qreal maxAccel = v2 / (2.0f * maxDistance); + if (maxAccel < accel) { + qreal dist = v2 / (accel * 2.0); + if (v > 0) + dist = -dist; + flickTargetX = -snapPosAt(-(_moveX.value() - highlightRangeStart) + dist) + highlightRangeStart; + dist = -flickTargetX + _moveX.value(); + accel = v2 / (2.0f * qAbs(dist)); + overshootDist = 0.0; + } else { + if (velocity > 0) + flickTargetX = minX; + else + flickTargetX = maxX; + overshootDist = overShoot ? 30 : 0; + } + timeline.reset(_moveX); + timeline.accel(_moveX, v, accel, maxDistance); + timeline.execute(fixupXEvent); flicked = true; emit q->flickingChanged(); emit q->flickStarted(); + correctFlick = true; + } else { + // reevaluate the target boundary. + qreal newtarget = -snapPosAt(-(flickTargetX - highlightRangeStart)) + highlightRangeStart; + if (newtarget < maxX) { + newtarget = maxX; + } + if (newtarget == flickTargetX) { + // boundary unchanged - nothing to do + return; + } + flickTargetX = newtarget; + qreal dist = -newtarget + _moveX.value(); + if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) { + correctFlick = false; + timeline.reset(_moveX); + fixupX(); + return; + } + timeline.reset(_moveX); + timeline.accelDistance(_moveX, v, -dist + (v < 0 ? -overshootDist : overshootDist)); + timeline.execute(fixupXEvent); } } else { + correctFlick = false; timeline.reset(_moveX); fixupX(); } @@ -901,23 +1033,38 @@ void QmlGraphicsListViewPrivate::flickY(qreal velocity) { Q_Q(QmlGraphicsListView); - if (!haveHighlightRange || highlightRange != QmlGraphicsListView::StrictlyEnforceRange) { + if ((!haveHighlightRange || highlightRange != QmlGraphicsListView::StrictlyEnforceRange) && snapMode == QmlGraphicsListView::NoSnap) { QmlGraphicsFlickablePrivate::flickY(velocity); return; } - qreal maxDistance = -1; + const qreal maxY = q->maxYExtent(); + const qreal minY = q->minYExtent(); // -ve velocity means list is moving up if (velocity > 0) { - if (_moveY.value() < q->minYExtent()) - maxDistance = qAbs(q->minYExtent() -_moveY.value() + (overShoot?30:0)); - flickTargetY = q->minYExtent(); + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = firstVisibleItem()) + maxDistance = qAbs(item->position() + _moveY.value()); + } else if (_moveY.value() < minY) { + maxDistance = qAbs(minY -_moveY.value() + (overShoot?30:0)); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetY = minY; } else { - if (_moveY.value() > q->maxYExtent()) - maxDistance = qAbs(q->maxYExtent() - _moveY.value()) + (overShoot?30:0); - flickTargetY = q->maxYExtent(); - } - if (maxDistance > 0) { + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = nextVisibleItem()) + maxDistance = qAbs(item->position() + _moveY.value()); + } else if (_moveY.value() > maxY) { + maxDistance = qAbs(maxY - _moveY.value()) + (overShoot?30:0); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetY = maxY; + } + if (maxDistance > 0 && (snapMode != QmlGraphicsListView::NoSnap || highlightRange == QmlGraphicsListView::StrictlyEnforceRange)) { + // These modes require the list to stop exactly on an item boundary. + // The initial flick will estimate the boundary to stop on. + // Since list items can have variable sizes, the boundary will be + // reevaluated and adjusted as we approach the boundary. qreal v = velocity; if (maxVelocity != -1 && maxVelocity < qAbs(v)) { if (v < 0) @@ -925,31 +1072,57 @@ void QmlGraphicsListViewPrivate::flickY(qreal velocity) else v = maxVelocity; } - qreal accel = deceleration; - qreal v2 = v * v; - qreal maxAccel = v2 / (2.0f * maxDistance); - if (maxAccel < accel) { - // If we are not flicking to the end then attempt to stop exactly on an item boundary - qreal dist = v2 / accel / 2.0; - if (v > 0) - dist = -dist; - dist = -_moveY.value() - snapPosAt(-(_moveY.value() - highlightRangeStart) + dist) + highlightRangeStart; - if ((v < 0 && dist >= 0) || (v > 0 && dist <= 0)) { - timeline.reset(_moveY); - fixupY(); - return; - } - accel = v2 / (2.0f * qAbs(dist)); - } - timeline.reset(_moveY); - timeline.accel(_moveY, v, accel, maxDistance); - timeline.execute(fixupYEvent); if (!flicked) { + // the initial flick - estimate boundary + qreal accel = deceleration; + qreal v2 = v * v; + qreal maxAccel = v2 / (2.0f * maxDistance); + if (maxAccel < accel) { + qreal dist = v2 / (accel * 2.0); + if (v > 0) + dist = -dist; + flickTargetY = -snapPosAt(-(_moveY.value() - highlightRangeStart) + dist) + highlightRangeStart; + dist = -flickTargetY + _moveY.value(); + accel = v2 / (2.0f * qAbs(dist)); + overshootDist = 0.0; + } else { + if (velocity > 0) + flickTargetY = minY; + else + flickTargetY = maxY; + overshootDist = overShoot ? 30 : 0; + } + timeline.reset(_moveY); + timeline.accel(_moveY, v, accel, maxDistance); + timeline.execute(fixupYEvent); flicked = true; emit q->flickingChanged(); emit q->flickStarted(); + correctFlick = true; + } else { + // reevaluate the target boundary. + qreal newtarget = -snapPosAt(-(flickTargetY - highlightRangeStart)) + highlightRangeStart; + if (newtarget < maxY) { + newtarget = maxY; + } + if (newtarget == flickTargetY) { + // boundary unchanged - nothing to do + return; + } + flickTargetY = newtarget; + qreal dist = -newtarget + _moveY.value(); + if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) { + correctFlick = false; + timeline.reset(_moveY); + fixupY(); + return; + } + timeline.reset(_moveY); + timeline.accelDistance(_moveY, v, -dist + (v < 0 ? -overshootDist : overshootDist)); + timeline.execute(fixupYEvent); } } else { + correctFlick = false; timeline.reset(_moveY); fixupY(); } @@ -1557,6 +1730,35 @@ void QmlGraphicsListView::setHighlightResizeSpeed(qreal speed) } } +/*! + \qmlproperty enumeration ListView::snapMode + + This property determines where the view will settle following a drag or flick. + The allowed values are: + + \list + \o NoSnap (default) - the view will stop anywhere within the visible area. + \o SnapToItem - the view will settle with an item aligned with the start of + the view. + \o SnapOneItem - the view will settle no more than one item away from the first + visible item at the time the mouse button is released. This mode is particularly + useful for moving one page at a time. + \endlist +*/ +QmlGraphicsListView::SnapMode QmlGraphicsListView::snapMode() const +{ + Q_D(const QmlGraphicsListView); + return d->snapMode; +} + +void QmlGraphicsListView::setSnapMode(SnapMode mode) +{ + Q_D(QmlGraphicsListView); + if (d->snapMode != mode) { + d->snapMode = mode; + } +} + void QmlGraphicsListView::viewportMoved() { Q_D(QmlGraphicsListView); @@ -1580,6 +1782,29 @@ void QmlGraphicsListView::viewportMoved() d->updateCurrent(idx); } } + + if ((d->haveHighlightRange && d->highlightRange == QmlGraphicsListView::StrictlyEnforceRange) + || d->snapMode == QmlGraphicsListView::SnapToItem) { + if (d->flicked && d->correctFlick) { + // Near an end and it seems that the extent has changed? + // Recalculate the flick so that we don't end up in an odd position. + if (d->velocityY > 0) { + if (d->flickTargetY - d->_moveY.value() < height()/2 && minYExtent() != d->flickTargetY) + d->flickY(-d->verticalVelocity.value()); + } else if (d->velocityY < 0) { + if (d->_moveY.value() - d->flickTargetY < height()/2 && maxYExtent() != d->flickTargetY) + d->flickY(-d->verticalVelocity.value()); + } + + if (d->velocityX > 0) { + if (d->flickTargetX - d->_moveX.value() < height()/2 && minXExtent() != d->flickTargetX) + d->flickX(-d->verticalVelocity.value()); + } else if (d->velocityX < 0) { + if (d->_moveX.value() - d->flickTargetX < height()/2 && maxXExtent() != d->flickTargetX) + d->flickX(-d->verticalVelocity.value()); + } + } + } } qreal QmlGraphicsListView::minYExtent() const diff --git a/src/declarative/graphicsitems/qmlgraphicslistview_p.h b/src/declarative/graphicsitems/qmlgraphicslistview_p.h index 795c766..cc74056 100644 --- a/src/declarative/graphicsitems/qmlgraphicslistview_p.h +++ b/src/declarative/graphicsitems/qmlgraphicslistview_p.h @@ -82,8 +82,11 @@ class Q_DECLARATIVE_EXPORT QmlGraphicsListView : public QmlGraphicsFlickable Q_PROPERTY(QString sectionExpression READ sectionExpression WRITE setSectionExpression NOTIFY sectionExpressionChanged) Q_PROPERTY(QString currentSection READ currentSection NOTIFY currentSectionChanged) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode) + Q_ENUMS(HighlightRangeMode) Q_ENUMS(Orientation) + Q_ENUMS(SnapMode) Q_CLASSINFO("DefaultProperty", "data") public: @@ -142,6 +145,10 @@ public: qreal highlightResizeSpeed() const; void setHighlightResizeSpeed(qreal); + enum SnapMode { NoSnap, SnapToItem, SnapOneItem }; + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + static QmlGraphicsListViewAttached *qmlAttachedProperties(QObject *); public Q_SLOTS: -- cgit v0.12