summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Griebl <rgriebl@trolltech.com>2010-04-08 19:57:03 +0200
committerRobert Griebl <rgriebl@trolltech.com>2010-04-08 19:57:03 +0200
commitf557f9a5099a07dfa599e394c779558a7e1fb167 (patch)
tree6ac0739c4724e8562964300b8d3199ce384f2468
parentc1a713436556df407f6227acc2e4488a32fb43ee (diff)
support both exponential (eg. maemo5) and linear (eg. iphone) deceleration.
-rw-r--r--qkineticscroller.cpp71
-rw-r--r--qkineticscroller.h4
-rw-r--r--qkineticscroller_p.h5
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;