From d6b663fcd6230492728d7c4b4a304b209533232c Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Tue, 30 Mar 2010 12:02:17 +0200 Subject: initial commit - Scrolling and Backshooting states are still missing --- qkineticscroller.cpp | 657 +++++++++++++++++++++++++++++++++++++++++++++++++++ qkineticscroller.h | 152 ++++++++++++ qkineticscroller_p.h | 121 ++++++++++ 3 files changed, 930 insertions(+) create mode 100644 qkineticscroller.cpp create mode 100644 qkineticscroller.h create mode 100644 qkineticscroller_p.h diff --git a/qkineticscroller.cpp b/qkineticscroller.cpp new file mode 100644 index 0000000..cdc5e9c --- /dev/null +++ b/qkineticscroller.cpp @@ -0,0 +1,657 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +//#define KINETIC_SCROLLER_DEBUG + +#ifdef KINETIC_SCROLLER_DEBUG +# define qKSDebug qDebug +#else +# define qKSDebug while (false) qDebug +#endif + + +/*! + \class QKineticScroller + \brief The QKineticScroller class enables kinetic scrolling for any scrolling widget or graphics item. + \ingroup qtmaemo5 + \since 4.6 + \preliminary + + With kinetic scrolling, the user can push the widget in a given + direction and it will continue to scroll in this direction until it is + stopped either by the user or by friction. Aspects of inertia, friction + and other physical concepts can be changed in order to fine-tune an + intuitive user experience. + + To enable kinetic scrolling for a widget or graphics item, you need to + derive from this class and implement at least all the pure-virtual + functions. + + Qt for Maemo 5 already comes with two implementations for + QScrollArea and QWebView, and those kinetic scrollers are + automatically instantiated and attached to these widgets on creation. + In the QScrollArea case, the kinetic scroller is initially + disabled. However, for QItemView and QScrollArea derived classes + it is enabled by default. You can obtain these automatically created + objects via a dynamic property: + + \code + // disable the kinetic scroller on scrollArea + QKineticScroller *scroller = scrollArea->property("kineticScroller") + .value(); + if (scroller) + scroller->setEnabled(false); + \endcode + + In addition there is also an example on how you would add kinetic + scrolling to a QGraphicsView based application in \c maemobrowser + examples in the \c maemo5 examples directory. + + The kinetic scroller installs an event filter on the widget to handle mouse + presses and moves on the widget \mdash presses and moves on a device's touch screen + are also handled by this mechanism. These events will be interpreted as scroll actions + depending on the current state() of the scroller. + + Even though this kinetic scroller has a huge number of settings, we + recommend that you leave them all at their default values. In case you + really want to change them you can try out the \c kineticscroller + example in the \c maemo5 examples directory. + + \sa QWidget +*/ + + +/*! + Constructs a new kinetic scroller. +*/ +QKineticScroller::QKineticScroller() + : d_ptr(new QKineticScrollerPrivate()) +{ + Q_D(QKineticScroller); + d->q_ptr = this; + d->init(); +} + +/*! \internal +*/ +QKineticScroller::QKineticScroller(QKineticScrollerPrivate &dd) + : d_ptr(&dd) +{ + Q_D(QKineticScroller); + d->q_ptr = this; + d->init(); +} + +/*! + Destroys the scroller. +*/ +QKineticScroller::~QKineticScroller() +{ +} + +/*! + Resets the internal state of the kinetic scroller. This function is not + needed for normal use. This function only needs to be called if the + kinetic scroller is being re-attached to a different widget. +*/ +void QKineticScroller::reset() +{ + Q_D(QKineticScroller); + + d->setState(Inactive); +} + +/*! + \enum QKineticScroller::State + + This enum describes the possible states the kinetic scroller can be in. + + \value Inactive The scroller is inactive. It may also have been disabled. + \value MousePressed The user has pressed the mouse button (or pressed the + the touch screen). + \value Pushing The user is dragging the mouse cursor (or other input + point) over the scroll area. + \value AutoScrolling Scrolling is occurring without direct user input. +*/ + + +static const char *stateName(QKineticScroller::State state) +{ + switch (state) { + case QKineticScroller::StateInactive: return "inactive"; + case QKineticScroller::StatePressed: return "pressed"; + case QKineticScroller::StateDragging: return "dragging"; + case QKineticScroller::StateScrolling: return "scrolling"; + case QKineticScroller::StateBackshooting: return "backshooting"; + default: return "(invalid)"; +} + +static const char *inputName(QKineticScroller::Input input) +{ + switch (input) { + case QKineticScroller::InputPress: return "press"; + case QKineticScroller::InputMove: return "move"; + case QKineticScroller::InputRelease: return "release"; + default: return "(invalid)"; +} + + + + +void QKineticScrollerPrivate::timerEvent(QTimerEvent *e) +{ + Q_Q(QKineticScroller); + + if (e->timerId() != timerId) { + QObject::timerEvent(e); + return; + } + + struct timerevent { + State state; + bool (QKineticScrollerPrivate::*handler)(qint64 elapsed); + }; + + timerevent timerevents[] = { + { StateDragging, QKineticScrollerPrivate::timerEventWhileDragging } + { StateScrolling, QKineticScrollerPrivate::timerEventWhileScrolling } + { StateBackshooting, QKineticScrollerPrivate::timerEventWhileBackshooting } + } + + for (int i = 0; i < sizeof(timerevents) / sizeof(*timerevents); ++i) { + timerevent *te = timerevents + i; + + if (state == te->state) { + te->handler(elapsed.restart()); + return; + } + } + + qWarning() << "Unhandled timer event, while in state " << stateName(state); + killTimer(timerId); + timerId = 0; +} + +bool QKineticScroller::handleInput(Input input, const QPointF &position, qint64 timestamp) +{ + Q_D(QKineticScroller); + + struct statechange { + State state; + Input input; + bool (QKineticScrollerPrivate::*handler)(Input input, const QPointF &position, qint64 timestamp); + }; + + statechange statechanges[] = { + { StateInactive, InputPress, QKineticScrollerPrivate::pressWhileInactive }, + { StatePressed, InputMove, QKineticScrollerPrivate::moveWhilePressed }, + { StatePressed, InputRelease, QKineticScrollerPrivate::releaseWhilePressed }, + { StateDragging, InputMove, QKineticScrollerPrivate::moveWhileDragging }, + { StateDragging, InputRelease, QKineticScrollerPrivate::releaseWhileDragging }, + { StateScrolling, InputPress, QKineticScrollerPrivate::pressWhileScrolling } + } + + for (int i = 0; i < sizeof(statechanges) / sizeof(*statechanges); ++i) { + statchange *sc = statechanges + i; + + if (d->state == sc->state && input == sc->input) + return sc->handler(input, position, timestamp); + } + + qWarning() << "Unhandled input: got input " << d->inputName(input) << " while in state " << d->stateName(d->state); + return false; +} + + + +QVariant QKineticScroller::scrollMetrics(ScrollMetrics metric) const +{ + Q_D(QKineticScroller); + + switch (metric) { + case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor; + case FrictionCoefficent: return d->frictionCoefficent; + case OvershootFrictionCoefficent: return d->overshootFricitionCoefficient; + case OvershootSpringConstant: return d->overshootSpringConstant; + case OvershootMaximumDistance: return d->overshootMaximumDistance; + case DragStartDistance: return d->dragStartDistance; + case DragStartDirectionErrorMargin: return d->dragStartDirectionErrorMargin; + case MinimumVelocity: return d->minimumVelocity; + case MaximumVelocity: return d->maximumVelocity; + case MaximumNonAcceleratedVelocity: return d->maximumNonAcceleratedVelocity; + case MaximumClickThroughVelocity: return d->maximumClickThroughVelocity; + case AxisLockThreshold: return d->axisLockThreshold; + case FramesPerMillisecond: return d->framesPerMillisecond; + } + return QVariant(); +} + + +void QKineticScroller::setScrollMetrics(ScrollMetrics metric, const QVariant &value) +{ + Q_D(QKineticScroller); + + switch (metric) { + case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(0, value.toReal(), 1); break; + case FrictionCoefficent: d->frictionCoefficent = qBound(0, value.toReal(), 1); break; + case OvershootFrictionCoefficent: d->overshootFricitionCoefficient = qBound(0, value.toReal(), 1); break; + case OvershootSpringConstant: d->overshootSpringConstant = value.toReal(); break; + case OvershootMaximumDistance: d->overshootMaximumDistance = value.toPointF(); break; + case DragStartDistance: d->dragStartDistance = value.toReal(); break; + case DragStartDirectionErrorMargin: d->dragStartDirectionErrorMargin = value.toReal(); break; + case MinimumVelocity: d->minimumVelocity = value.toReal(); break; + case MaximumVelocity: d->maximumVelocity = value.toReal(); break; + case MaximumNonAcceleratedVelocity: d->maximumNonAcceleratedVelocity = value.toReal(); break; + case MaximumClickThroughVelocity: d->maximumClickThroughVelocity = value.toReal(); break; + case AxisLockThreshold: d->axisLockThreshold = qBound(0, value.toReal(), 1); break; + case FramesPerMillisecond: d->framesPerMillisecond = qBound(1, value.toInt(), 100); break; + } +} + +void QKineticScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + QPointF deltaPixel = position - lastPostion; + qint64 deltaTime = timestamp - lastTimestamp; + + if (axisLockThreshold) { + int dx = qAbs(deltaPixel.x()); + int dy = qAbs(deltaPixel.y()); + if (dx || dy) { + bool vertical = (dy > dx); + qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx); + //qKSDebug() << "axis lock: " << alpha << " / " << axisLockThreshold << " - isvertical: " << vertical << " - dx: " << dx << " - dy: " << dy; + if (alpha <= axisLockThreshold) { + if (vertical) + deltaPixel.setX(0); + else + deltaPixel.setY(0); + } + } + } + + // calculate velocity (if the user would release the mouse NOW) + QPointF newVelocity = calculateVelocity(deltaPixel, deltaTime); + + // restrict velocity, if content is not scrollable + QPoint maxPos = q->maximumContentPosition(); + bool canScrollX = maxPos.x() || (overshootPolicy == QKineticScroller::OvershootAlwaysOn); + bool canScrollY = maxPos.y() || (overshootPolicy == QKineticScroller::OvershootAlwaysOn); + + if (!canScrolX)) { + deltaPixel.setX(0); + newVelocity.setX(0); + } + if (!canScrollY) { + deltaPixel.setY(0); + newVelocity.setY(0); + } + velocity = newVelocity; + +// if (firstDrag) { +// // Do not delay the first drag +// setScrollPositionHelper(q->contentPosition() - overshootDistance - deltaPixel); +// dragDistance = QPointF(0, 0); +// } else { + dragDistance += deltaPixel; +// } + + if (canScrollX) + lastPosition.setX(position.x()); + if (canScrollY) + lastPosition.setY(position.y()); + lastTimestamp = timestamp; +} + + +bool operator<=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f); +} +bool operator<(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) < f) && (qAbs(p.y()) < f); +} + +bool operator>=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f); +} +bool operator>(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) > f) || (qAbs(p.y()) > f); +} + +QPointF qAbs(const QPointF &p) +{ + return QPointF(qAbs(p.x()), qAbs(p.y())); +} + + + +bool QKineticScrollerPrivate::pressWhileInactive(Input, const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + if ((q->maximumContentPosition() > qreal(0)) || (overshootPolicy == QKineticScroller::OvershootAlwaysOn)) + if (q->canStartScrollingAt(position)) { + lastPosition = pressPosition = position; + lastTimestamp = pressTimestamp = timestamp; + setState(QKineticScroller::StatePressed); + } + } +} + +bool QKineticScrollerPrivate::releaseWhilePressed(Input, const QPointF &position, qint64 timestamp) +{ + setState(QKineticScroller::StateInactive); + return false; +} + +bool QKineticScrollerPrivate::moveWhilePressed(Input, const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + QPointF deltaPixel = position - pressPosition; + + bool moveStarted = (deltaPixel.manhattanLength() > dragStartDistance); + + if (moveStarted) { + qreal deltaXtoY = qAbs(pressPosition.x() - position.x()) - qAbs(pressPosition.y() - position.y()); + + QPointF maxPos = q->maximumContentPosition(); + bool canScrollX = (maxPos.x() > 0); + bool canScrollY = (maxPos.y() > 0); + + if (overshootPolicy == QKineticScroller::OvershootAlwaysOn) + canScrollX = canScrollY = true; + + if (deltaXtoY < 0) { + if (!canScrollY && (!canScrollX || (-deltaXtoY >= dragStartDirectionErrorMargin))) + moveStarted = false; + } else { + if (!canScrollX && (!canScrollY || (deltaXtoY >= dragStartDirectionErrorMargin))) + moveStarted = false; + } + } + + if (moveStarted) { + q->cancelPress(pressPosition); + setState(QKineticScroller::Dragging); + + // ignore the dragStartDistance + deltaPixel = deltaPixel - deltaPixel * (dragStartDistance / deltaPixel.manhattanLength()); + + if (!deltaPixel.isNull()) { + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(pressPosition + deltaPixel, timestamp); + } + } + return moveStarted; +} + +bool QKineticScrollerPrivate::moveWhileDragging(Input, const QPointF &position, qint64 timestamp) +{ + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(position, timestamp); + return true; +} + +void QKineticScrollerPrivate::timerEventWhileDragging(qint64 timestamp) +{ + if (!dragDistance.isNull()) + setScrollPositionHelper(q->contentPosition() - overshootDistance - dragDistance); +} + +bool QKineticScrollerPrivate::releaseWhileDragging(Input, const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + // ... + + setState(QKineticScroller::AutoScrolling); + return true; +} + +void QKineticScrollerPrivate::timerEventWhileScrolling(qint64 timestamp) +{ + Q_Q(QKineticScroller); +} + +bool QKineticScrollerPrivate::pressWhileScrolling(Input, const QPointF &position, qint64 timestamp) +{ + oldVelocity = velocity; + velocity = QPointF(0, 0); + + setState(QKineticScroller::Pressed); + return true; +} + +void QKineticScrollerPrivate::timerEventWhileBackshooting(qint64 timestamp) +{ + Q_Q(QKineticScroller); +} + + +void QKineticScrollerPrivate::setState(QKineticScroller::State newstate) +{ + if (state == newstate) + return; + + bool startTimer = false, stopTimer = false; + + switch (state) { + case QKineticScroller::StatePressed: + if (newstate == QKineticScroller::Dragging) { + if (!timerId) { + timerId = startTimer(1000 / framesPerSecond); + } else { + qWarning() << "State change from " << stateName(state) << " to " << stateName(newstate) << ", but timer is already activate."; + } + break; + case QKineticScroller::Scrolling: + case QKineticScroller::Backshooting: + if (newstate == QKineticScroller::Inactive) { + if (timerId) { + killTimer(timerId); + timerId = 0; + } else { + qWarning() << "State change from " << stateName(state) << " to " << stateName(newstate) << ", but timer is not activate."; + } + } + break; + } + + switch (newstate) { + case QKineticScroller::Inactive: + case QKineticScroller::Pressed: + velocity = QPointF(0, 0); + break; + case QKineticScroller::Dragging: + dragDistance = QPointF(0, 0); + break; + } + + qSwap(state, newstate); + emit stateChanged(newstate); +} + + +/* + Decomposes the position into a scroll and an overshoot part. + Also keeps track of the current over-shooting value in overshootDist. +*/ +void QKineticScrollerPrivate::setScrollPositionHelper(const QPoint &pos) +{ + Q_Q(QKineticScroller); + + QPointF maxPos = q->maximumScrollPosition(); + + QPointF clampedPos; + clampedPos.setX(qBound(0, pos.x(), maxPos.x())); + clampedPos.setY(qBound(0, pos.y(), maxPos.y())); + + bool alwaysOvershoot = (overshootPolicy == QKineticScroller::OvershootAlwaysOn); + qreal overshootX = (maxPos.x() || alwaysOvershoot) ? clampedPos.x() - pos.x() : 0; + qreal overshootY = (maxPos.y() || alwaysOvershoot) ? clampedPos.y() - pos.y() : 0; + + if (overshootMaximumDistance) { + overshootDistance.setX(qBound(-overshootMaximumDistance.x(), overshootX, overshootMaximumDistance.x())); + overshootDistance.setY(qBound(-overshootMaximumDistance.y(), overshootY, overshootMaximumDistance.y())); + } else { + overshootDistance = QPointF(overshootX, overshootY); + } + + qKSDebug() << "setPosition raw: " << pos << ", clamped: " << clampedPos << ", overshoot: " << overshootDist; + q->setScrollPosition(clampedPos, overshootPolicy == QKineticScroller::OvershootAlwaysOff ? QPoint() : overshootDistance); +} + + +/*! + \enum QKineticScroller::OvershootPolicy + + This enum describes the various modes of overshooting. + + \value OvershootWhenScrollable Overshooting is when the content is scrollable. This is the default. + + \value OvershootAlwaysOff Overshooting is never enabled (even when the content is scrollable). + + \value OvershootAlwaysOn Overshooting is always enabled (even when the content is not scrollable). +*/ + + +/*! + If kinetic scrolling can be started at the given content's \a position, + this function needs to return true; otherwise it needs to return false. + + The default value is true, regardless of \a position. +*/ +bool QKineticScroller::canStartScrollingAt(const QPointF &position) const +{ + Q_UNUSED(position); + return true; +} + +/*! + Since a mouse press is always delivered normally when the scroller is in + the StateInactive state, we may need to cancel it as soon as the user + has moved the mouse far enough to actually start a kinetic scroll + operation. + + The \a pressPosition parameter can be used to find out which widget (or + graphics item) received the mouse press in the first place. + + Subclasses may choose to simulate a fake mouse release event for that + widget (or graphics item), preferably \bold not within its boundaries. + The default implementation does nothing. +*/ +void QKineticScroller::cancelPress(const QPointF &pressPosition) +{ + Q_UNUSED(pressPosition); +} + + +/*! + This function get called whenever the state of the kinetic scroller changes. + The old state is supplied as \a oldState, while the new state is returned by + calling state(). + + The default implementation does nothing. + + \sa state() +*/ +void QKineticScroller::stateChanged(State oldState) +{ + Q_UNUSED(oldState); +} + +/*! + \fn QPointF QKineticScroller::maximumContentPosition() const + + Returns the maximum valid content position. The minimum is always \c + (0,0). + + \sa scrollTo() +*/ + +/*! + \fn QSizeF QKineticScroller::viewportSize() const + + Returns the size of the currently visible content positions. In the + case where an QAbstractScrollArea is used, this is equivalent to the + viewport() size. + + \sa scrollTo() +*/ + +/*! + \fn QPointF QKineticScroller::contentPosition() const + + \brief Returns the current position of the content. + + Note that overshooting is not considered to be "real" scrolling so the + position might be (0,0) even if the user is currently dragging the + widget outside the "normal" maximumContentPosition(). + + \sa maximumContentPosition() +*/ + + +/*! + \fn void QKineticScroller::setContentPosition(const QPointF &position, const QPointF &overshoot) + + Set the content's \a position. This parameter will always be in the + valid range QPointF(0, 0) and maximumContentPosition(). + + In the case where overshooting is required, the \a overshoot parameter + will give the direction and the absolute distance to overshoot. + + \sa maximumContentPosition() +*/ + +QT_END_NAMESPACE diff --git a/qkineticscroller.h b/qkineticscroller.h new file mode 100644 index 0000000..3210e11 --- /dev/null +++ b/qkineticscroller.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTKINETICSCROLLER_H +#define QABSTRACTKINETICSCROLLER_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QKineticScrollerPrivate; + +class Q_GUI_EXPORT QKineticScroller +{ +public: + ~QKineticScroller(); + + bool isEnabled() const; + void setEnabled(bool b); + + enum State + { + StateInactive, + StatePressed, + StateDragging, + StateScrolling, + StateBackshooting + }; + + State state() const; + + enum OvershootPolicy + { + OvershootWhenScrollable, + OvershootAlwaysOff, + OvershootAlwaysOn, + }; + + OvershootPolicy overshootPolicy() const; + void setOvershootPolicy(OvershootPolicy policy); + + enum ScrollMetric + { + DragVelocitySmoothingFactor, // [0..1] v = v_new* DASF + v_old * (1-DASF) + + /* A factor between \c 0 and \c 1 */ + + FrictionCoefficent, // [0..1] + OvershootFrictionCoefficent, // [0..1] + OvershootSpringConstant, // [0...[ + OvershootMaximumDistance, // ([m], [m]) + + DragStartDistance, // [m] + DragStartDirectionErrorMargin, // [] dx/dy + + MinimumVelocity, // [m/s] + MaximumVelocity, // [m/s] + MaximumNonAcceleratedVelocity, // [m/s] + + MaximumClickThroughVelocity, // [m/s] + //fastVelocityFactor???? + + AxisLockThreshold, // [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) + + FramesPerMilliSecond, // [frames/s] + + FastSwipeMaximumTimeBetweenReleases, // [s] + FastSwipeMinimumVelocity, // [m/s] + FastSwipeAccelerationFactor, // [m/s^2] + }; + + QVariant scrollMetric(ScrollMetric metric) const; + void setScrollMetric(ScrollMetric metric, const QVariant &value); + +protected: + virtual QSizeF viewportSize() const = 0; + virtual QPointF maximumContentPosition() const = 0; + virtual QPointF contentPosition() const = 0; + virtual void setContentPosition(const QPointF &pos, const QPointF &overshootDelta) = 0; + + virtual void stateChanged(State oldState); + virtual bool canStartScrollingAt(const QPointF &pos) const; + virtual void cancelPress(const QPointF &pressPos); + + enum Input { + InputPress, + InputMove, + InputRelease + }; + + bool handleInput(Input input, const QPointF &position, qint64 timestamp) + + QKineticScroller(QKineticScrollerPrivate &dd); + QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QKineticScroller) + Q_DECLARE_PRIVATE(QKineticScroller) + +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QKineticScroller *) + +QT_END_HEADER + +#endif // QKINETICSCROLLER_H diff --git a/qkineticscroller_p.h b/qkineticscroller_p.h new file mode 100644 index 0000000..8b4b99b --- /dev/null +++ b/qkineticscroller_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QKineticScrollerPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(QKineticScroller) + +public: + QKineticScrollerPrivate(); + virtual ~QKineticScrollerPrivate(); + void init(); + + void setState(QKineticScroller::State s); + + bool pressWhileInactive(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool moveWhilePressed(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool releaseWhilePressed(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool moveWhileDragging(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool releaseWhileDragging(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool pressWhileScrolling(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + + void timerEvent(QTimerEvent *); + void timerEventWhileDragging(qint64 timestamp); + void timerEventWhileScrolling(qint64 timestamp); + void timerEventWhileBackshooting(qint64 timestamp); + + void handleDrag(const QPointF &position, qint64 timestamp); + void setScrollPositionHelper(const QPoint &position); + + // metrics + + qreal dragVelocitySmoothingFactor; + qreal frictionCoefficent; + qreal overshootFricitionCoefficient; + qreal overshootSpringConstant; + QPointF overshootMaximumDistance; + qreal dragStartDistance; + qreal dragStartDirectionErrorMargin; + qreal maximumVelocity; + qreal minimumVelocity; + qreal maximumNonAcceleratedVelocity; + qreal maximumClickThroughVelocity; + qreal axisLockThreshold; + int framesPerMillisecond; + + // state + + bool enabled; + QKineticScroller::State state; + QPointF velocity; + QPointF oldVelocity; + + QPointF pressPosition; + QPointF lastPosition; + qint64 lastTimestamp; + + QPointF dragDistance; + QPointF overshootDistance; + + int timerId; +}; + +QT_END_NAMESPACE -- cgit v1.2.3