diff options
author | Robert Griebl <robert.griebl@nokia.com> | 2011-01-24 11:45:03 (GMT) |
---|---|---|
committer | Robert Griebl <robert.griebl@nokia.com> | 2011-01-24 13:12:58 (GMT) |
commit | 7bad867382ad6c84155ffcfbb361709a8e8184ab (patch) | |
tree | 14b588967d87f428fe27539ff0eee38c86d00bb8 /src/gui/util | |
parent | 4810b587a65d81f8f90646efd09cadeb1276a756 (diff) | |
download | Qt-7bad867382ad6c84155ffcfbb361709a8e8184ab.zip Qt-7bad867382ad6c84155ffcfbb361709a8e8184ab.tar.gz Qt-7bad867382ad6c84155ffcfbb361709a8e8184ab.tar.bz2 |
Fixed overshooting with a very low start velocity.
Before this patch, the slow down (overshoot) was a separate scroll segment.
This could lead to an actual acceleration when going into overshoot with a
very low velocity. The new approach is to just continue with the normal
scroll segment, until the max overshoot distance is reached and only add
one additional segment for the bounce-back animation to complete the
overshoot animation.
Reviewed-by: Ralf Engels
Diffstat (limited to 'src/gui/util')
-rw-r--r-- | src/gui/util/qscroller.cpp | 153 | ||||
-rw-r--r-- | src/gui/util/qscroller_p.h | 5 |
2 files changed, 87 insertions, 71 deletions
diff --git a/src/gui/util/qscroller.cpp b/src/gui/util/qscroller.cpp index 2eb6d2f..7173270 100644 --- a/src/gui/util/qscroller.cpp +++ b/src/gui/util/qscroller.cpp @@ -83,9 +83,9 @@ bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); QDebug &operator<<(QDebug &dbg, const QScrollerPrivate::ScrollSegment &s) { - dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime; - dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos; - dbg << "\n Curve: type:" << s.curve.type() << " max progress:" << s.maxProgress << "\n"; + dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime << " stop progress:" << s.stopProgress; + dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos << " stop:" << s.stopPos; + dbg << "\n Curve: type:" << s.curve.type() << "\n"; return dbg; } @@ -601,18 +601,17 @@ QPointF QScroller::velocity() const if (!d->xSegments.isEmpty()) { const QScrollerPrivate::ScrollSegment &s = d->xSegments.head(); - qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); - qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / s.maxProgress / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); + qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime); + qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); vel.setX(v); } if (!d->ySegments.isEmpty()) { const QScrollerPrivate::ScrollSegment &s = d->ySegments.head(); - qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); - qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / s.maxProgress / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); + qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime); + qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); vel.setY(v); } - //qScrollerDebug() << "Velocity: " << vel; return vel; } default: @@ -1083,8 +1082,23 @@ void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 delta deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength(); QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm; - if (releaseVelocity != QPointF(0, 0)) - newv = newv * sp->dragVelocitySmoothingFactor + releaseVelocity * (qreal(1) - sp->dragVelocitySmoothingFactor); + // around 95% of all updates are in the [1..50] ms range, so make sure + // to scale the smoothing factor over that range: this way a 50ms update + // will have full impact, while 5ms update will only have a 10% impact. + qreal smoothing = sp->dragVelocitySmoothingFactor * qMin(qreal(deltaTime), qreal(50)) / qreal(50); + + // only smooth if we already have a release velocity and only if the + // user hasn't stopped to move his finger for more than 100ms + if ((releaseVelocity != QPointF(0, 0)) && (deltaTime < 100)) { + qScrollerDebug() << "SMOOTHED from " << newv << " to " << newv * smoothing + releaseVelocity * (qreal(1) - smoothing); + // smooth x or y only if the new velocity is either 0 or at least in + // the same direction of the release velocity + if (!newv.x() || (qSign(releaseVelocity.x()) == qSign(newv.x()))) + newv.setX(newv.x() * smoothing + releaseVelocity.x() * (qreal(1) - smoothing)); + if (!newv.y() || (qSign(releaseVelocity.y()) == qSign(newv.y()))) + newv.setY(newv.y() * smoothing + releaseVelocity.y() * (qreal(1) - smoothing)); + } else + qScrollerDebug() << "NO SMOOTHING to " << newv; releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity)); releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity)); @@ -1092,23 +1106,24 @@ void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 delta qScrollerDebug() << " --> new velocity:" << releaseVelocity; } -void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal startPos, qreal endPos, QEasingCurve::Type curve, Qt::Orientation orientation, qreal maxProgress) +void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation) { - if (startPos == endPos) + if (startPos == stopPos || deltaPos == 0) return; ScrollSegment s; if (orientation == Qt::Horizontal && !xSegments.isEmpty()) - s.startTime = xSegments.last().startTime + xSegments.last().deltaTime; + s.startTime = xSegments.last().startTime + xSegments.last().deltaTime * xSegments.last().stopProgress; else if (orientation == Qt::Vertical && !ySegments.isEmpty()) - s.startTime = ySegments.last().startTime + ySegments.last().deltaTime; + s.startTime = ySegments.last().startTime + ySegments.last().deltaTime * ySegments.last().stopProgress; else s.startTime = monotonicTimer.elapsed(); s.startPos = startPos; - s.deltaPos = endPos - startPos; + s.deltaPos = deltaPos; + s.stopPos = stopPos; s.deltaTime = deltaTime * 1000; - s.maxProgress = maxProgress; + s.stopProgress = stopProgress; s.curve.setType(curve); s.type = type; @@ -1143,23 +1158,17 @@ void QScrollerPrivate::recalcScrollingSegments(bool forceRecalc) */ qreal QScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const { - const QQueue<ScrollSegment> *segments; - qreal endPos; - if (orientation == Qt::Horizontal) { - segments = &xSegments; - endPos = contentPosition.x() + overshootPosition.x(); + if (xSegments.isEmpty()) + return contentPosition.x() + overshootPosition.x(); + else + return xSegments.last().stopPos; } else { - segments = &ySegments; - endPos = contentPosition.y() + overshootPosition.y(); - } - - if (!segments->isEmpty()) { - const ScrollSegment &last = segments->last(); - endPos = last.startPos + last.deltaPos; + if (ySegments.isEmpty()) + return contentPosition.y() + overshootPosition.y(); + else + return ySegments.last().stopPos; } - - return endPos; } /*! \internal @@ -1185,23 +1194,23 @@ bool QScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation) return true; const ScrollSegment &last = segments->last(); - qreal endPos = last.startPos + last.deltaPos; + qreal stopPos = last.stopPos; if (last.type == ScrollTypeScrollTo) return true; // scrollTo is always valid if (last.type == ScrollTypeOvershoot && - endPos != minPos && endPos != maxPos) + (stopPos != minPos && stopPos != maxPos)) return false; - if (endPos < minPos || endPos > maxPos) + if (stopPos < minPos || stopPos > maxPos) return false; - if (endPos == minPos || endPos == maxPos) // the begin and the end of the list are always ok + if (stopPos == minPos || stopPos == maxPos) // the begin and the end of the list are always ok return true; - qreal nextSnap = nextSnapPos(endPos, 0, orientation); - if (!qIsNaN(nextSnap) && endPos != nextSnap) + qreal nextSnap = nextSnapPos(stopPos, 0, orientation); + if (!qIsNaN(nextSnap) && stopPos != nextSnap) return false; return true; @@ -1214,11 +1223,10 @@ void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal en { Q_UNUSED(v); - if (orientation == Qt::Horizontal) { + if (orientation == Qt::Horizontal) xSegments.clear(); - } else { + else ySegments.clear(); - } qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation); @@ -1226,10 +1234,10 @@ void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal en qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x() : contentPosition.y() + overshootPosition.y(); - qreal deltaPos = endPos - startPos; + qreal deltaPos = (endPos - startPos) / 2; - pushSegment(type, deltaTime * 0.3, startPos, startPos + deltaPos * 0.5, QEasingCurve::InQuad, orientation); - pushSegment(type, deltaTime * 0.7, startPos + deltaPos * 0.5, endPos, sp->scrollingCurve.type(), orientation); + pushSegment(type, deltaTime * 0.3, 1.0, startPos, deltaPos, startPos + deltaPos, QEasingCurve::InQuad, orientation); + pushSegment(type, deltaTime * 0.7, 1.0, startPos + deltaPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation); } /*! \internal @@ -1298,17 +1306,13 @@ void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal pp if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos)) lowerSnapPos = nextSnap; - // -- check if we end in overshoot + // -- check if are in overshoot and end in overshoot if ((startPos < minPos && endPos < minPos) || (startPos > maxPos && endPos > maxPos)) { qreal stopPos = endPos < minPos ? minPos : maxPos; - qreal oDistance = viewSize * sp->overshootScrollDistanceFactor * v / sp->maximumVelocity; qreal oDeltaTime = sp->overshootScrollTime; - if (qAbs(v) > sp->minimumVelocity) - pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.5, startPos, startPos + oDistance, sp->scrollingCurve.type(), orientation); - pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.3, startPos + oDistance, stopPos + oDistance * 0.3, QEasingCurve::InQuad, orientation); - pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.2, stopPos + oDistance * 0.3, stopPos, QEasingCurve::OutQuad, orientation); + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.7, 1.0, startPos, stopPos - startPos, stopPos, sp->scrollingCurve.type(), orientation); return; } @@ -1337,8 +1341,9 @@ void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal pp endPos = higherSnapPos; deltaPos = endPos - startPos; - pushSegment(ScrollTypeFlick, sp->snapTime * 0.3, startPos, startPos + deltaPos * 0.3, QEasingCurve::InQuad, orientation); - pushSegment(ScrollTypeFlick, sp->snapTime * 0.7, startPos + deltaPos * 0.3, endPos, sp->scrollingCurve.type(), orientation); + qreal midPos = startPos + deltaPos * 0.3; + pushSegment(ScrollTypeFlick, sp->snapTime * 0.3, 1.0, startPos, midPos - startPos, midPos, QEasingCurve::InQuad, orientation); + pushSegment(ScrollTypeFlick, sp->snapTime * 0.7, 1.0, midPos, endPos - midPos, endPos, sp->scrollingCurve.type(), orientation); return; } @@ -1365,28 +1370,33 @@ void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal pp qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos); - qreal maxProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos)); - qScrollerDebug() << "Overshoot maxp:" << maxProgress; + qreal stopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos)); - pushSegment(ScrollTypeFlick, deltaTime * maxProgress, startPos, stopPos, sp->scrollingCurve.type(), orientation, maxProgress); + if (!canOvershoot) { + qScrollerDebug() << "Overshoot stopp:" << stopProgress; - if (canOvershoot) { - qreal endV = qSign(v) * deltaTime * sp->decelerationFactor * qreal(0.5) * differentialForProgress(sp->scrollingCurve, maxProgress); - qScrollerDebug() << "Overshoot: velocity" << endV; - qScrollerDebug() << "Overshoot: maxVelocity" << sp->maximumVelocity; - qScrollerDebug() << "Overshoot: viewsize" << viewSize; - qScrollerDebug() << "Overshoot: factor" << sp->overshootScrollDistanceFactor; - - qreal oDistance = viewSize * sp->overshootScrollDistanceFactor * endV / sp->maximumVelocity; + pushSegment(ScrollTypeFlick, deltaTime, stopProgress, startPos, endPos, stopPos, sp->scrollingCurve.type(), orientation); + } else { qreal oDeltaTime = sp->overshootScrollTime; + qreal oStopProgress = qMin(stopProgress + oDeltaTime * 0.3 / deltaTime, qreal(1)); + qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(oStopProgress) - stopPos; + qreal oMaxDistance = qSign(oDistance) * (viewSize * sp->overshootScrollDistanceFactor); - pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.3, stopPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation); - pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.7, stopPos + oDistance, stopPos, sp->scrollingCurve.type(), orientation); + qScrollerDebug() << "1 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress; + + if (qAbs(oDistance) > qAbs(oMaxDistance)) { + oStopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos + oMaxDistance - startPos) / deltaPos)); + oDistance = oMaxDistance; + qScrollerDebug() << "2 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress; + } + + pushSegment(ScrollTypeFlick, deltaTime, oStopProgress, startPos, deltaPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation); + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.7, 1.0, stopPos + oDistance, -oDistance, stopPos, sp->scrollingCurve.type(), orientation); } return; } - pushSegment(ScrollTypeFlick, deltaTime, startPos, endPos, sp->scrollingCurve.type(), orientation); + pushSegment(ScrollTypeFlick, deltaTime, 1.0, startPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation); } @@ -1810,13 +1820,18 @@ qreal QScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qin while (!segments.isEmpty()) { const ScrollSegment s = segments.head(); - if ((s.startTime + s.deltaTime) <= now) { + if ((s.startTime + s.deltaTime * s.stopProgress) <= now) { segments.dequeue(); - pos = s.startPos + s.deltaPos; + pos = s.stopPos; } else if (s.startTime <= now) { - qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); - pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress) / s.curve.valueForProgress(s.maxProgress); - break; + qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime); + pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress); + if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) { + segments.dequeue(); + pos = s.stopPos; + } else { + break; + } } else { break; } diff --git a/src/gui/util/qscroller_p.h b/src/gui/util/qscroller_p.h index 98f34f7..d16eef9 100644 --- a/src/gui/util/qscroller_p.h +++ b/src/gui/util/qscroller_p.h @@ -98,7 +98,8 @@ public: qreal startPos; qreal deltaPos; QEasingCurve curve; - qreal maxProgress; + qreal stopProgress; // whatever is.. + qreal stopPos; // ..reached first ScrollType type; }; @@ -122,7 +123,7 @@ public: void setDpiFromWidget(QWidget *widget); void updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime); - void pushSegment(ScrollType type, qreal deltaTime, qreal startPos, qreal endPos, QEasingCurve::Type curve, Qt::Orientation orientation, qreal maxProgress = 1.0); + void pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation); void recalcScrollingSegments(bool forceRecalc = false); qreal scrollingSegmentsEndPos(Qt::Orientation orientation) const; bool scrollingSegmentsValid(Qt::Orientation orientation); |