diff options
author | Robert Griebl <rgriebl@trolltech.com> | 2010-04-08 19:57:03 +0200 |
---|---|---|
committer | Robert Griebl <rgriebl@trolltech.com> | 2010-04-08 19:57:03 +0200 |
commit | f557f9a5099a07dfa599e394c779558a7e1fb167 (patch) | |
tree | 6ac0739c4724e8562964300b8d3199ce384f2468 | |
parent | c1a713436556df407f6227acc2e4488a32fb43ee (diff) |
support both exponential (eg. maemo5) and linear (eg. iphone) deceleration.
-rw-r--r-- | qkineticscroller.cpp | 71 | ||||
-rw-r--r-- | qkineticscroller.h | 4 | ||||
-rw-r--r-- | qkineticscroller_p.h | 5 |
3 files changed, 50 insertions, 30 deletions
diff --git a/qkineticscroller.cpp b/qkineticscroller.cpp index 46d1bc8..04b0b83 100644 --- a/qkineticscroller.cpp +++ b/qkineticscroller.cpp @@ -43,6 +43,7 @@ #include <QMap> #include <QApplication> #include <QDesktopWidget> +#include <QtCore/qmath.h> #include <QtDebug> @@ -170,7 +171,8 @@ void QKineticScroller::resetScrollMetrics() #ifdef Q_WS_MAEMO_5 metrics.insert(DragVelocitySmoothingFactor, qreal(0.15)); - metrics.insert(FrictionCoefficent, qreal(0.85)); + metrics.insert(ExponentialDecelerationBase, qreal(0.38)); // 0.85^20 + metrics.insert(LinearDecelerationFactor, qreal(0)); metrics.insert(OvershootSpringConstant, qreal(0.1)); metrics.insert(OvershootDragResistanceFactor, qreal(1)); metrics.insert(OvershootMaximumDistance, QPointF(qreal(150), qreal(150))); @@ -188,7 +190,8 @@ void QKineticScroller::resetScrollMetrics() metrics.insert(FramesPerSecond, qreal(20)); #else metrics.insert(DragVelocitySmoothingFactor, qreal(0.02)); - metrics.insert(FrictionCoefficent, qreal(0.05)); + metrics.insert(ExponentialDecelerationBase, qreal(0)); + metrics.insert(LinearDecelerationFactor, qreal(0.38)); metrics.insert(OvershootSpringConstant, qreal(15.0)); metrics.insert(OvershootDragResistanceFactor, qreal(0.5)); metrics.insert(OvershootMaximumDistance, QPointF(0,0)); // QPointF(qreal(14.25 / 1000), qreal(14.25 / 1000))); @@ -361,7 +364,8 @@ QVariant QKineticScroller::scrollMetric(ScrollMetric metric) const switch (metric) { case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor; - case FrictionCoefficent: return d->frictionCoefficent; + case LinearDecelerationFactor: return d->linearDecelerationFactor; + case ExponentialDecelerationBase: return d->exponentialDecelerationBase; case OvershootSpringConstant: return d->overshootSpringConstant; case OvershootDragResistanceFactor: return d->overshootDragResistanceFactor; case OvershootMaximumDistance: return d->overshootMaximumDistance; @@ -389,7 +393,8 @@ void QKineticScroller::setScrollMetric(ScrollMetric metric, const QVariant &valu switch (metric) { case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; - case FrictionCoefficent: d->frictionCoefficent = qBound(qreal(0), value.toReal(), qreal(1)); break; + case LinearDecelerationFactor: d->linearDecelerationFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case ExponentialDecelerationBase: d->exponentialDecelerationBase = qBound(qreal(0), value.toReal(), qreal(1)); break; case OvershootSpringConstant: d->overshootSpringConstant = value.toReal(); break; case OvershootDragResistanceFactor: d->overshootDragResistanceFactor = value.toReal(); break; case OvershootMaximumDistance: d->overshootMaximumDistance = value.toPointF(); break; @@ -681,14 +686,25 @@ bool QKineticScrollerPrivate::releaseWhileDragging(QKineticScroller::Input, cons return true; } -void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time) +qreal QKineticScrollerPrivate::decelerate(qreal v, qreal expDecel, qreal linDecel) +{ + v *= expDecel; + if (qAbs(v) >= linDecel) + return v + (v < 0 ? linDecel : -linDecel); + else + return 0;; +} + +void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time_ms) { Q_Q(QKineticScroller); + qreal time = qreal(time_ms) / 1000; + // --- handle overshooting and stop if the coordinate is going back inside the normal area if (!overshootDistance.isNull()) { // calculate the new overshoot distance (convert from [m/s] to [pixel/frame] - QPointF newOvershootDistance = overshootDistance + velocity * qreal(time) / 1000 * pixelPerMeter; + QPointF newOvershootDistance = overshootDistance + velocity * time * pixelPerMeter; qKSDebug() << "Overshooting, old: "<<overshootDistance<<"new:"<<newOvershootDistance; @@ -705,16 +721,17 @@ void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time) } } + qWarning() << "Pos: " << q->contentPosition() << " - Vel: " << velocity << " - Time: " << qreal(time) << " - PPM: " << pixelPerMeter; + // -- move (convert from [m/s] to [pix/frame] - setContentPositionHelper(q->contentPosition() + overshootDistance + velocity * qreal(time) / 1000 * pixelPerMeter); + setContentPositionHelper(q->contentPosition() + overshootDistance + velocity * time * pixelPerMeter); // --- (de)accelerate overshooting axis using spring constant or friction // actually we would need to calculate a root and not just divide - qreal forcePerFrame = overshootSpringConstant * qreal(time) / 1000; - qreal frictionPerFrame = (qreal(1) - ((qreal(1) - frictionCoefficent) * qreal(time) / 1000)); - - qWarning() << "frictionPerFrame: " << frictionPerFrame; + qreal forcePerFrame = overshootSpringConstant * time; + qreal exponentialDecelerationPerFrame = (qreal(1) - ((qreal(1) - exponentialDecelerationBase) * time)); + qreal linearDecelerationPerFrame = linearDecelerationFactor * time; // -- x coordinate if (overshootDistance.x()) { @@ -723,7 +740,7 @@ void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time) } else if (scrollToX) { if (qAbs(velocity.x()) >= qreal(3.0 / 1000 /* 3mm/s */)) - velocity.rx() *= frictionPerFrame; + velocity.setX(decelerate(velocity.x(), exponentialDecelerationPerFrame, linearDecelerationPerFrame)); // - target reached? qreal dist = scrollToPosition.x() - q->contentPosition().x(); @@ -733,7 +750,7 @@ void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time) } } else { - velocity.rx() *= frictionPerFrame; + velocity.setX(decelerate(velocity.x(), exponentialDecelerationPerFrame, linearDecelerationPerFrame)); } // -- y coordinate @@ -743,7 +760,7 @@ void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time) } else if (scrollToY) { if (qAbs(velocity.y()) >= qreal(3.0 / 1000 /* 3mm/s */)) - velocity.ry() *= frictionPerFrame; + velocity.setY(decelerate(velocity.y(), exponentialDecelerationPerFrame, linearDecelerationPerFrame)); // - target reached? qreal dist = scrollToPosition.y() - q->contentPosition().y(); @@ -753,10 +770,10 @@ void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 time) } } else { - velocity.ry() *= frictionPerFrame; + velocity.setY(decelerate(velocity.y(), exponentialDecelerationPerFrame, linearDecelerationPerFrame)); } - if (overshootDistance.isNull() && (velocity <= qreal(2.0 / 1000 /* 2mm/s */)) && !scrollToX && !scrollToY) + if (overshootDistance.isNull() && (velocity < qreal(0.5) * qreal(framesPerSecond) / pixelPerMeter /* 1 [pix/frame] */) && !scrollToX && !scrollToY) setState(QKineticScroller::StateInactive); } @@ -854,23 +871,21 @@ void QKineticScroller::scrollTo(const QPointF &pos, int scrollTime) return; } - // --- calculate the initial velocity to get to the point - // -- calc the number of steps we have: - int steps = scrollTime * d->framesPerSecond / 1000; + // v(t) = vstart * exponentialDecelerationBase ^ t + linearDecelerationFactor * t + // pos(t) = integrate(v(t) * dt) + // pos(t) = vstart * eDB ^ t / ln(eDB) + lDF / 2 * t ^ 2 + // + // pos(scrollTime) = pos - contentsPos() + // vstart = (lDF / 2 * scrollTime ^ 2 - (pos - contentPos())) * ln(eDB) / eDB ^ scrollTime - // -- calc the distance we will move with a starting velocity of 1.0 - qreal dist = qreal(0); - qreal curVel = qreal(1); - for (int i = 0; i < steps; i++) { - dist += curVel; - curVel *= d->frictionCoefficent; - } + QPointF v = QPointF(1, 1) * d->linearDecelerationFactor / 2 * scrollTime * scrollTime - (pos - contentPosition()) * + qLn(d->exponentialDecelerationBase) / qPow(d->exponentialDecelerationBase, scrollTime); - // --- start the scrolling + // start the scrolling d->scrollToPosition = pos; d->scrollToX = true; d->scrollToY = true; - d->velocity = (pos - contentPosition()) / dist / d->framesPerSecond * 1000 / d->pixelPerMeter; + d->velocity = v; d->setState(QKineticScroller::StateScrolling); } diff --git a/qkineticscroller.h b/qkineticscroller.h index 3429d79..3b31b88 100644 --- a/qkineticscroller.h +++ b/qkineticscroller.h @@ -87,7 +87,9 @@ public: enum ScrollMetric { DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) - FrictionCoefficent, // qreal [0..1] + + LinearDecelerationFactor, // qreal [0..1] + ExponentialDecelerationBase, // qreal [0..1] OvershootSpringConstant, // qreal [0..1] OvershootDragResistanceFactor, // qreal [0..1] OvershootMaximumDistance, // QPointF([m], [m]) diff --git a/qkineticscroller_p.h b/qkineticscroller_p.h index 14188ce..cb06feb 100644 --- a/qkineticscroller_p.h +++ b/qkineticscroller_p.h @@ -86,13 +86,16 @@ public: void updateVelocity(const QPointF &deltaPixel, qint64 deltaTime); void setContentPositionHelper(const QPointF &position); + qreal decelerate(qreal v, qreal expDecel, qreal linDecel); + static const char *stateName(QKineticScroller::State state); static const char *inputName(QKineticScroller::Input input); // metrics qreal dragVelocitySmoothingFactor; - qreal frictionCoefficent; + qreal linearDecelerationFactor; + qreal exponentialDecelerationBase; qreal overshootSpringConstant; qreal overshootDragResistanceFactor; QPointF overshootMaximumDistance; |