From c1e668a96764d1a252322ebae3af72097c27dfe1 Mon Sep 17 00:00:00 2001 From: Martin Jones Date: Tue, 31 Jan 2012 09:12:13 +1000 Subject: [PATCH] Multiple fast flicks with large content moves faster 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 --- src/quick/items/qquickflickable.cpp | 86 ++++++++++++++++++---- src/quick/items/qquickflickable_p_p.h | 5 +- .../qtquick2/qquickflickable/data/flickable03.qml | 4 +- .../qquickflickable/tst_qquickflickable.cpp | 18 ++++- 4 files changed, 94 insertions(+), 19 deletions(-) diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index f581909..6215500 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -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(); } diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h index 0cd7113..e4290f2 100644 --- a/src/quick/items/qquickflickable_p_p.h +++ b/src/quick/items/qquickflickable_p_p.h @@ -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 velocityBuffer; bool atEnd : 1; @@ -198,6 +200,7 @@ public: QBasicTimer delayedPressTimer; int pressDelay; int fixupDuration; + qreal flickBoost; enum FixupMode { Normal, Immediate, ExtentChanged }; FixupMode fixupMode; diff --git a/tests/auto/qtquick2/qquickflickable/data/flickable03.qml b/tests/auto/qtquick2/qquickflickable/data/flickable03.qml index ebc49ba..719c682 100644 --- a/tests/auto/qtquick2/qquickflickable/data/flickable03.qml +++ b/tests/auto/qtquick2/qquickflickable/data/flickable03.qml @@ -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" } } } diff --git a/tests/auto/qtquick2/qquickflickable/tst_qquickflickable.cpp b/tests/auto/qtquick2/qquickflickable/tst_qquickflickable.cpp index 3fa822b..4439d97 100644 --- a/tests/auto/qtquick2/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/qtquick2/qquickflickable/tst_qquickflickable.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #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 -- 2.7.4