Multiple fast flicks with large content moves faster
authorMartin Jones <martin.jones@nokia.com>
Mon, 30 Jan 2012 23:12:13 +0000 (09:12 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 2 Feb 2012 02:55:21 +0000 (03:55 +0100)
Repeatedly flicking quickly in a large view moves faster than the velocity
of the touch.

Task-number: QTBUG-18600
Change-Id: Ie6747e0d945022e0c0c5f6c5f95bc35403a14d56
Reviewed-by: Martin Jones <martin.jones@nokia.com>
src/quick/items/qquickflickable.cpp
src/quick/items/qquickflickable_p_p.h
tests/auto/qtquick2/qquickflickable/data/flickable03.qml
tests/auto/qtquick2/qquickflickable/tst_qquickflickable.cpp

index f581909..6215500 100644 (file)
@@ -83,6 +83,21 @@ QT_BEGIN_NAMESPACE
 #define QML_FLICK_OVERSHOOTFRICTION 8
 #endif
 
+// Multiflick acceleration minimum flick velocity threshold
+#ifndef QML_FLICK_MULTIFLICK_THRESHOLD
+#define QML_FLICK_MULTIFLICK_THRESHOLD 1250
+#endif
+
+// Multiflick acceleration minimum contentSize/viewSize ratio
+#ifndef QML_FLICK_MULTIFLICK_RATIO
+#define QML_FLICK_MULTIFLICK_RATIO 10
+#endif
+
+// Multiflick acceleration maximum velocity multiplier
+#ifndef QML_FLICK_MULTIFLICK_MAXBOOST
+#define QML_FLICK_MULTIFLICK_MAXBOOST 3.0
+#endif
+
 // FlickThreshold determines how far the "mouse" must have moved
 // before we perform a flick.
 static const int FlickThreshold = 20;
@@ -179,7 +194,7 @@ QQuickFlickablePrivate::QQuickFlickablePrivate()
     , deceleration(QML_FLICK_DEFAULTDECELERATION)
     , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100)
     , delayedPressEvent(0), delayedPressTarget(0), pressDelay(0), fixupDuration(400)
-    , fixupMode(Normal), vTime(0), visibleArea(0)
+    , flickBoost(1.0), fixupMode(Normal), vTime(0), visibleArea(0)
     , flickableDirection(QQuickFlickable::AutoFlickDirection)
     , boundsBehavior(QQuickFlickable::DragAndOvershootBounds)
 {
@@ -664,6 +679,11 @@ void QQuickFlickable::setInteractive(bool interactive)
     The instantaneous velocity of movement along the x and y axes, in pixels/sec.
 
     The reported velocity is smoothed to avoid erratic output.
+
+    Note that for views with a large content size (more than 10 times the view size),
+    the velocity of the flick may exceed the velocity of the touch in the case
+    of multiple quick consecutive flicks.  This allows the user to flick faster
+    through large content.
 */
 qreal QQuickFlickable::horizontalVelocity() const
 {
@@ -803,8 +823,23 @@ void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event)
         && (qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity
             || qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity)) {
         stealMouse = true; // If we've been flicked then steal the click.
+        int flickTime = timeline.time();
+        if (flickTime > 600) {
+            // too long between flicks - cancel boost
+            hData.continuousFlickVelocity = 0;
+            vData.continuousFlickVelocity = 0;
+            flickBoost = 1.0;
+        } else {
+            hData.continuousFlickVelocity = -hData.smoothVelocity.value();
+            vData.continuousFlickVelocity = -vData.smoothVelocity.value();
+            if (flickTime > 300) // slower flicking - reduce boost
+                flickBoost = qMax(1.0, flickBoost - 0.5);
+        }
     } else {
         stealMouse = false;
+        hData.continuousFlickVelocity = 0;
+        vData.continuousFlickVelocity = 0;
+        flickBoost = 1.0;
     }
     q->setKeepMouseGrab(stealMouse);
     pressed = true;
@@ -817,13 +852,12 @@ void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event)
     vData.dragMaxBound = q->maxYExtent();
     fixupMode = Normal;
     lastPos = QPointF();
-    lastPosTime = computeCurrentTime(event);
     pressPos = event->localPos();
     hData.pressPos = hData.move.value();
     vData.pressPos = vData.move.value();
     hData.flicking = false;
     vData.flicking = false;
-    lastPressTime = computeCurrentTime(event);
+    lastPosTime = lastPressTime = computeCurrentTime(event);
     QQuickItemPrivate::start(velocityTime);
 }
 
@@ -960,24 +994,46 @@ void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event)
 
     vTime = timeline.time();
 
-    qreal velocity = elapsed < 100 ? vData.velocity : 0;
-    if (vData.atBeginning || vData.atEnd)
-        velocity /= 2;
-    if (q->yflick() && qAbs(velocity) > MinimumFlickVelocity && qAbs(event->localPos().y() - pressPos.y()) > FlickThreshold) {
+    bool canBoost = false;
+
+    qreal vVelocity = elapsed < 100 ? vData.velocity : 0;
+    if (vData.atBeginning || vData.atEnd) {
+        vVelocity /= 2;
+    } else if (vData.continuousFlickVelocity != 0.0
+               && vData.viewSize/q->height() > QML_FLICK_MULTIFLICK_RATIO
+               && ((vVelocity > 0) == (vData.continuousFlickVelocity > 0))
+               && qAbs(vVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) {
+        // accelerate flick for large view flicked quickly
+        canBoost = true;
+    }
+
+    qreal hVelocity = elapsed < 100 ? hData.velocity : 0;
+    if (hData.atBeginning || hData.atEnd) {
+        hVelocity /= 2;
+    } else if (hData.continuousFlickVelocity != 0.0
+               && hData.viewSize/q->width() > QML_FLICK_MULTIFLICK_RATIO
+               && ((hVelocity > 0) == (hData.continuousFlickVelocity > 0))
+               && qAbs(hVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) {
+        // accelerate flick for large view flicked quickly
+        canBoost = true;
+    }
+
+    flickBoost = canBoost ? qBound(1.0, flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0;
+
+    vVelocity *= flickBoost;
+    if (q->yflick() && qAbs(vVelocity) > MinimumFlickVelocity && qAbs(event->localPos().y() - pressPos.y()) > FlickThreshold) {
         velocityTimeline.reset(vData.smoothVelocity);
-        vData.smoothVelocity.setValue(-velocity);
-        flickY(velocity);
+        vData.smoothVelocity.setValue(-vVelocity);
+        flickY(vVelocity);
     } else {
         fixupY();
     }
 
-    velocity = elapsed < 100 ? hData.velocity : 0;
-    if (hData.atBeginning || hData.atEnd)
-        velocity /= 2;
-    if (q->xflick() && qAbs(velocity) > MinimumFlickVelocity && qAbs(event->localPos().x() - pressPos.x()) > FlickThreshold) {
+    hVelocity *= flickBoost;
+    if (q->xflick() && qAbs(hVelocity) > MinimumFlickVelocity && qAbs(event->localPos().x() - pressPos.x()) > FlickThreshold) {
         velocityTimeline.reset(hData.smoothVelocity);
-        hData.smoothVelocity.setValue(-velocity);
-        flickX(velocity);
+        hData.smoothVelocity.setValue(-hVelocity);
+        flickX(hVelocity);
     } else {
         fixupX();
     }
index 0cd7113..e4290f2 100644 (file)
@@ -71,7 +71,7 @@ QT_BEGIN_NAMESPACE
 const qreal MinimumFlickVelocity = 75.0;
 
 class QQuickFlickableVisibleArea;
-class QQuickFlickablePrivate : public QQuickItemPrivate, public QQuickItemChangeListener
+class Q_AUTOTEST_EXPORT QQuickFlickablePrivate : public QQuickItemPrivate, public QQuickItemChangeListener
 {
     Q_DECLARE_PUBLIC(QQuickFlickable)
 
@@ -97,6 +97,7 @@ public:
     struct AxisData {
         AxisData(QQuickFlickablePrivate *fp, void (QQuickFlickablePrivate::*func)(qreal))
             : move(fp, func), viewSize(-1), startMargin(0), endMargin(0)
+            , continuousFlickVelocity(0)
             , smoothVelocity(fp), atEnd(false), atBeginning(true)
             , fixingUp(false), inOvershoot(false), moving(false), flicking(false)
             , dragging(false), extentsChanged(false)
@@ -129,6 +130,7 @@ public:
         qreal flickTarget;
         qreal startMargin;
         qreal endMargin;
+        qreal continuousFlickVelocity;
         QQuickFlickablePrivate::Velocity smoothVelocity;
         QPODVector<qreal,10> velocityBuffer;
         bool atEnd : 1;
@@ -198,6 +200,7 @@ public:
     QBasicTimer delayedPressTimer;
     int pressDelay;
     int fixupDuration;
+    qreal flickBoost;
 
     enum FixupMode { Normal, Immediate, ExtentChanged };
     FixupMode fixupMode;
index ebc49ba..719c682 100644 (file)
@@ -1,13 +1,13 @@
 import QtQuick 2.0
 
 Flickable {
-    width: 100; height: 200
+    width: 100; height: 400
     contentWidth: column.width; contentHeight: column.height
 
     Column {
         id: column
         Repeater {
-            model: 4
+            model: 20
             Rectangle { width: 200; height: 300; color: "blue" }
         }
     }
index 3fa822b..4439d97 100644 (file)
@@ -44,6 +44,7 @@
 #include <QtDeclarative/qdeclarativecomponent.h>
 #include <QtQuick/qquickview.h>
 #include <private/qquickflickable_p.h>
+#include <private/qquickflickable_p_p.h>
 #include <private/qdeclarativevaluetype_p.h>
 #include <math.h>
 #include "../../shared/util.h"
@@ -131,7 +132,7 @@ void tst_qquickflickable::verticalViewportSize()
 
     QVERIFY(obj != 0);
     QCOMPARE(obj->contentWidth(), 200.);
-    QCOMPARE(obj->contentHeight(), 1200.);
+    QCOMPARE(obj->contentHeight(), 6000.);
     QCOMPARE(obj->isAtXBeginning(), true);
     QCOMPARE(obj->isAtXEnd(), false);
     QCOMPARE(obj->isAtYBeginning(), true);
@@ -538,6 +539,20 @@ void tst_qquickflickable::flickVelocity()
     QVERIFY(flickable->verticalVelocity() < 0.0);
     QTRY_VERIFY(flickable->verticalVelocity() == 0.0);
 
+    // Flick multiple times and verify that flick acceleration is applied.
+    QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(flickable);
+    bool boosted = false;
+    for (int i = 0; i < 6; ++i) {
+        flick(canvas, QPoint(20,390), QPoint(20, 50), 200);
+        boosted |= fp->flickBoost > 1.0;
+    }
+    QVERIFY(boosted);
+
+    // Flick in opposite direction -> boost cancelled.
+    flick(canvas, QPoint(20,10), QPoint(20, 340), 200);
+    QTRY_VERIFY(flickable->verticalVelocity() < 0.0);
+    QVERIFY(fp->flickBoost == 1.0);
+
     delete canvas;
 }
 
@@ -616,6 +631,7 @@ void tst_qquickflickable::flick(QQuickView *canvas, const QPoint &from, const QP
     }
 
     QTest::mouseRelease(canvas, Qt::LeftButton, 0, to);
+    QTest::qWait(50);
 }
 
 template<typename T>