1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
44 #include "qscroller.h"
45 #include "private/qflickgesture_p.h"
46 #include "private/qscroller_p.h"
47 #include "qscrollerproperties.h"
48 #include "private/qscrollerproperties_p.h"
53 #include <QElapsedTimer>
55 #include <QApplication>
56 #include <QAbstractScrollArea>
57 #include <QGraphicsObject>
58 #include <QGraphicsScene>
59 #include <QGraphicsView>
60 #include <QDesktopWidget>
61 #include <QtCore/qmath.h>
62 #include <QtGui/qevent.h>
68 # include "private/qt_x11_p.h"
74 bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
76 //#define QSCROLLER_DEBUG
78 #ifdef QSCROLLER_DEBUG
79 # define qScrollerDebug qDebug
81 # define qScrollerDebug while (false) qDebug
84 QDebug &operator<<(QDebug &dbg, const QScrollerPrivate::ScrollSegment &s)
86 dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime << " stop progress:" << s.stopProgress;
87 dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos << " stop:" << s.stopPos;
88 dbg << "\n Curve: type:" << s.curve.type() << "\n";
93 // a few helper operators to make the code below a lot more readable:
94 // otherwise a lot of ifs would have to be multi-line to check both the x
95 // and y coordinate separately.
97 // returns true only if the abs. value of BOTH x and y are <= f
98 inline bool operator<=(const QPointF &p, qreal f)
100 return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f);
103 // returns true only if the abs. value of BOTH x and y are < f
104 inline bool operator<(const QPointF &p, qreal f)
106 return (qAbs(p.x()) < f) && (qAbs(p.y()) < f);
109 // returns true if the abs. value of EITHER x or y are >= f
110 inline bool operator>=(const QPointF &p, qreal f)
112 return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f);
115 // returns true if the abs. value of EITHER x or y are > f
116 inline bool operator>(const QPointF &p, qreal f)
118 return (qAbs(p.x()) > f) || (qAbs(p.y()) > f);
121 // returns a new point with both coordinates having the abs. value of the original one
122 inline QPointF qAbs(const QPointF &p)
124 return QPointF(qAbs(p.x()), qAbs(p.y()));
127 // returns a new point with all components of p1 multiplied by the corresponding components of p2
128 inline QPointF operator*(const QPointF &p1, const QPointF &p2)
130 return QPointF(p1.x() * p2.x(), p1.y() * p2.y());
133 // returns a new point with all components of p1 divided by the corresponding components of p2
134 inline QPointF operator/(const QPointF &p1, const QPointF &p2)
136 return QPointF(p1.x() / p2.x(), p1.y() / p2.y());
139 inline QPointF clampToRect(const QPointF &p, const QRectF &rect)
141 qreal x = qBound(rect.left(), p.x(), rect.right());
142 qreal y = qBound(rect.top(), p.y(), rect.bottom());
143 return QPointF(x, y);
146 // returns -1, 0 or +1 according to r being <0, ==0 or >0
147 inline int qSign(qreal r)
149 return (r < 0) ? -1 : ((r > 0) ? 1 : 0);
152 // this version is not mathematically exact, but it just works for every
153 // easing curve type (even custom ones)
155 static qreal differentialForProgress(const QEasingCurve &curve, qreal pos)
157 const qreal dx = 0.01;
158 qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx);
159 qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx);
160 qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx);
162 //qScrollerDebug() << "differentialForProgress(type: " << curve.type() << ", pos: " << pos << ") = " << d;
167 // this version is not mathematically exact, but it just works for every
168 // easing curve type (even custom ones)
170 static qreal progressForValue(const QEasingCurve &curve, qreal value)
172 if (curve.type() >= QEasingCurve::InElastic &&
173 curve.type() < QEasingCurve::Custom) {
174 qWarning("progressForValue(): QEasingCurves of type %d do not have an inverse, since they are not injective.", curve.type());
177 if (value < qreal(0) || value > qreal(1))
180 qreal progress = value, left(0), right(1);
181 for (int iterations = 6; iterations; --iterations) {
182 qreal v = curve.valueForProgress(progress);
189 progress = (left + right) / qreal(2);
195 #ifndef QT_NO_ANIMATION
196 class QScrollTimer : public QAbstractAnimation
199 QScrollTimer(QScrollerPrivate *_d)
200 : d(_d), ignoreUpdate(false), skip(0)
210 // QAbstractAnimation::start() will immediately call
211 // updateCurrentTime(), but our state is not set correctly yet
213 QAbstractAnimation::start();
214 ignoreUpdate = false;
219 void updateCurrentTime(int /*currentTime*/)
222 if (++skip >= d->frameRateSkip()) {
234 #endif // QT_NO_ANIMATION
238 \brief The QScroller class enables kinetic scrolling for any scrolling widget or graphics item.
241 With kinetic scrolling, the user can push the widget in a given
242 direction and it will continue to scroll in this direction until it is
243 stopped either by the user or by friction. Aspects of inertia, friction
244 and other physical concepts can be changed in order to fine-tune an
245 intuitive user experience.
247 The QScroller object is the object that stores the current position and
248 scrolling speed and takes care of updates.
249 QScroller can be triggered by a flick gesture
253 QScroller::grabGesture(w, QScroller::LeftMouseButtonGesture);
256 or directly like this:
260 QScroller *scroller = QScroller::scroller(w);
261 scroller->scrollTo(QPointF(100, 100));
264 The scrolled QObjects receive a QScrollPrepareEvent whenever the scroller needs to
265 update its geometry information and a QScrollEvent whenever the content of the object should
266 actually be scrolled.
268 The scroller uses the global QAbstractAnimation timer to generate its QScrollEvents. This
269 can be changed with QScrollerProperties::FrameRate on a per-QScroller basis.
271 Several examples in the \c scroller examples directory show how QScroller,
272 QScrollEvent and the scroller gesture can be used.
274 Even though this kinetic scroller has a large number of settings available via
275 QScrollerProperties, we recommend that you leave them all at their default, platform optimized
276 values. Before changing them you can experiment with the \c plot example in
277 the \c scroller examples directory.
279 \sa QScrollEvent, QScrollPrepareEvent, QScrollerProperties
282 typedef QMap<QObject *, QScroller *> ScrollerHash;
283 typedef QSet<QScroller *> ScrollerSet;
285 Q_GLOBAL_STATIC(ScrollerHash, qt_allScrollers)
286 Q_GLOBAL_STATIC(ScrollerSet, qt_activeScrollers)
289 Returns \c true if a QScroller object was already created for \a target; \c false otherwise.
293 bool QScroller::hasScroller(QObject *target)
295 return (qt_allScrollers()->value(target));
299 Returns the scroller for the given \a target.
300 As long as the object exists this function will always return the same QScroller instance.
301 If no QScroller exists for the \a target, one will implicitly be created.
302 At no point more than one QScroller will be active on an object.
304 \sa hasScroller(), target()
306 QScroller *QScroller::scroller(QObject *target)
309 qWarning("QScroller::scroller() was called with a null target.");
313 if (qt_allScrollers()->contains(target))
314 return qt_allScrollers()->value(target);
316 QScroller *s = new QScroller(target);
317 qt_allScrollers()->insert(target, s);
323 This is the const version of scroller().
325 const QScroller *QScroller::scroller(const QObject *target)
327 return scroller(const_cast<QObject*>(target));
331 Returns an application wide list of currently active QScroller objects.
332 Active QScroller objects are in a state() that is not QScroller::Inactive.
333 This function is useful when writing your own gesture recognizer.
335 QList<QScroller *> QScroller::activeScrollers()
337 return qt_activeScrollers()->toList();
341 Returns the target object of this scroller.
342 \sa hasScroller(), scroller()
344 QObject *QScroller::target() const
346 Q_D(const QScroller);
351 \fn QScroller::scrollerPropertiesChanged(const QScrollerProperties &newProperties);
353 QScroller emits this signal whenever its scroller properties change.
354 \a newProperties are the new scroller properties.
356 \sa scrollerProperties
360 /*! \property QScroller::scrollerProperties
361 \brief The scroller properties of this scroller.
362 The properties are used by the QScroller to determine its scrolling behavior.
364 QScrollerProperties QScroller::scrollerProperties() const
366 Q_D(const QScroller);
367 return d->properties;
370 void QScroller::setScrollerProperties(const QScrollerProperties &sp)
373 if (d->properties != sp) {
375 emit scrollerPropertiesChanged(sp);
377 // we need to force the recalculation here, since the overshootPolicy may have changed and
378 // existing segments may include an overshoot animation.
379 d->recalcScrollingSegments(true);
383 #ifndef QT_NO_GESTURES
386 Registers a custom scroll gesture recognizer, grabs it for the \a
387 target and returns the resulting gesture type. If \a scrollGestureType is
388 set to TouchGesture the gesture triggers on touch events. If it is set to
389 one of LeftMouseButtonGesture, RightMouseButtonGesture or
390 MiddleMouseButtonGesture it triggers on mouse events of the
391 corresponding button.
393 Only one scroll gesture can be active on a single object at the same
394 time. If you call this function twice on the same object, it will
395 ungrab the existing gesture before grabbing the new one.
397 \note To avoid unwanted side-effects, mouse events are consumed while
398 the gesture is triggered. Since the initial mouse press event is
399 not consumed, the gesture sends a fake mouse release event
400 at the global position \c{(INT_MIN, INT_MIN)}. This ensures that
401 internal states of the widget that received the original mouse press
404 \sa ungrabGesture, grabbedGesture
406 Qt::GestureType QScroller::grabGesture(QObject *target, ScrollerGestureType scrollGestureType)
408 // ensure that a scroller for target is created
409 QScroller *s = scroller(target);
411 return Qt::GestureType(0);
413 QScrollerPrivate *sp = s->d_ptr;
415 ungrabGesture(target); // ungrab the old gesture
417 Qt::MouseButton button;
418 switch (scrollGestureType) {
419 case LeftMouseButtonGesture : button = Qt::LeftButton; break;
420 case RightMouseButtonGesture : button = Qt::RightButton; break;
421 case MiddleMouseButtonGesture: button = Qt::MiddleButton; break;
423 case TouchGesture : button = Qt::NoButton; break; // NoButton == Touch
426 sp->recognizer = new QFlickGestureRecognizer(button);
427 sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer);
429 if (target->isWidgetType()) {
430 QWidget *widget = static_cast<QWidget *>(target);
431 widget->grabGesture(sp->recognizerType);
432 if (scrollGestureType == TouchGesture)
433 widget->setAttribute(Qt::WA_AcceptTouchEvents);
434 #ifndef QT_NO_GRAPHICSVIEW
435 } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
436 if (scrollGestureType == TouchGesture)
437 go->setAcceptTouchEvents(true);
438 go->grabGesture(sp->recognizerType);
439 #endif // QT_NO_GRAPHICSVIEW
441 return sp->recognizerType;
445 Returns the gesture type currently grabbed for the \a target or 0 if no
448 \sa grabGesture, ungrabGesture
450 Qt::GestureType QScroller::grabbedGesture(QObject *target)
452 QScroller *s = scroller(target);
454 return s->d_ptr->recognizerType;
456 return Qt::GestureType(0);
460 Ungrabs the gesture for the \a target.
461 Does nothing if no gesture is grabbed.
463 \sa grabGesture, grabbedGesture
465 void QScroller::ungrabGesture(QObject *target)
467 QScroller *s = scroller(target);
471 QScrollerPrivate *sp = s->d_ptr;
473 return; // nothing to do
475 if (target->isWidgetType()) {
476 QWidget *widget = static_cast<QWidget *>(target);
477 widget->ungrabGesture(sp->recognizerType);
478 #ifndef QT_NO_GRAPHICSVIEW
479 } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
480 go->ungrabGesture(sp->recognizerType);
484 QGestureRecognizer::unregisterRecognizer(sp->recognizerType);
485 // do not delete the recognizer. The QGestureManager is doing this.
489 #endif // QT_NO_GESTURES
494 QScroller::QScroller(QObject *target)
495 : d_ptr(new QScrollerPrivate(this, target))
497 Q_ASSERT(target); // you can't create a scroller without a target in any normal way
505 QScroller::~QScroller()
508 #ifndef QT_NO_GESTURES
509 QGestureRecognizer::unregisterRecognizer(d->recognizerType);
510 // do not delete the recognizer. The QGestureManager is doing this.
513 qt_allScrollers()->remove(d->target);
514 qt_activeScrollers()->remove(this);
521 \fn QScroller::stateChanged(QScroller::State newState);
523 QScroller emits this signal whenever the state changes. \a newState is the new State.
529 \property QScroller::state
530 \brief the state of the scroller
534 QScroller::State QScroller::state() const
536 Q_D(const QScroller);
541 Stops the scroller and resets its state back to Inactive.
543 void QScroller::stop()
546 if (d->state != Inactive) {
547 QPointF here = clampToRect(d->contentPosition, d->contentPosRange);
548 qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal);
549 qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical);
555 d->contentPosition = snap;
556 d->overshootPosition = QPointF(0, 0);
558 d->setState(Inactive);
563 Returns the pixel per meter metric for the scrolled widget.
565 The value is reported for both the x and y axis separately by using a QPointF.
567 \note Please note that this value should be physically correct. The actual DPI settings
568 that Qt returns for the display may be reported wrongly on purpose by the underlying
569 windowing system, for example on Mac OS X or Maemo 5.
571 QPointF QScroller::pixelPerMeter() const
573 Q_D(const QScroller);
574 QPointF ppm = d->pixelPerMeter;
576 #ifndef QT_NO_GRAPHICSVIEW
577 if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->target)) {
579 //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
580 if (go->scene() && !go->scene()->views().isEmpty())
581 viewtr = go->scene()->views().first()->viewportTransform();
582 QTransform tr = go->deviceTransform(viewtr);
583 if (tr.isScaling()) {
584 QPointF p0 = tr.map(QPointF(0, 0));
585 QPointF px = tr.map(QPointF(1, 0));
586 QPointF py = tr.map(QPointF(0, 1));
587 ppm.rx() /= QLineF(p0, px).length();
588 ppm.ry() /= QLineF(p0, py).length();
591 #endif // QT_NO_GRAPHICSVIEW
596 Returns the current scrolling velocity in meter per second when the state is Scrolling or Dragging.
597 Returns a zero velocity otherwise.
599 The velocity is reported for both the x and y axis separately by using a QPointF.
603 QPointF QScroller::velocity() const
605 Q_D(const QScroller);
606 const QScrollerPropertiesPrivate *sp = d->properties.d.data();
610 return d->releaseVelocity;
613 qint64 now = d->monotonicTimer.elapsed();
615 if (!d->xSegments.isEmpty()) {
616 const QScrollerPrivate::ScrollSegment &s = d->xSegments.head();
617 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
618 qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
622 if (!d->ySegments.isEmpty()) {
623 const QScrollerPrivate::ScrollSegment &s = d->ySegments.head();
624 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
625 qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
631 return QPointF(0, 0);
636 Returns the estimated final position for the current scroll movement.
637 Returns the current position if the scroller state is not Scrolling.
638 The result is undefined when the scroller state is Inactive.
640 The target position is in pixel.
642 \sa pixelPerMeter(), scrollTo()
644 QPointF QScroller::finalPosition() const
646 Q_D(const QScroller);
647 return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
648 d->scrollingSegmentsEndPos(Qt::Vertical));
652 Starts scrolling the widget so that point \a pos is at the top-left position in
655 The behaviour when scrolling outside the valid scroll area is undefined.
656 In this case the scroller might or might not overshoot.
658 The scrolling speed will be calculated so that the given position will
659 be reached after a platform-defined time span.
661 \a pos is given in viewport coordinates.
665 void QScroller::scrollTo(const QPointF &pos)
667 // we could make this adjustable via QScrollerProperties
673 This version will reach its destination position in \a scrollTime milliseconds.
675 void QScroller::scrollTo(const QPointF &pos, int scrollTime)
679 if (d->state == Pressed || d->state == Dragging )
682 // no need to resend a prepare event if we are already scrolling
683 if (d->state == Inactive && !d->prepareScrolling(QPointF()))
686 QPointF newpos = clampToRect(pos, d->contentPosRange);
687 qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal);
688 qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical);
694 qScrollerDebug() << "QScroller::scrollTo(req:" << pos << " [pix] / snap:" << newpos << ", " << scrollTime << " [ms])";
696 if (newpos == d->contentPosition + d->overshootPosition)
699 QPointF vel = velocity();
703 qreal time = qreal(scrollTime) / 1000;
705 d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QScrollerPrivate::ScrollTypeScrollTo);
706 d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QScrollerPrivate::ScrollTypeScrollTo);
709 d->setContentPositionHelperScrolling();
710 d->setState(scrollTime ? Scrolling : Inactive);
714 Starts scrolling so that the rectangle \a rect is visible inside the
715 viewport with additional margins specified in pixels by \a xmargin and \a ymargin around
718 In cases where it is not possible to fit the rect plus margins inside the viewport the contents
719 are scrolled so that as much as possible is visible from \a rect.
721 The scrolling speed is calculated so that the given position is reached after a platform-defined
724 This function performs the actual scrolling by calling scrollTo().
728 void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin)
730 // we could make this adjustable via QScrollerProperties
731 ensureVisible(rect, xmargin, ymargin, 1000);
736 This version will reach its destination position in \a scrollTime milliseconds.
738 void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime)
742 if (d->state == Pressed || d->state == Dragging )
745 if (d->state == Inactive && !d->prepareScrolling(QPointF()))
748 // -- calculate the current pos (or the position after the current scroll)
749 QPointF startPos = d->contentPosition + d->overshootPosition;
750 startPos = QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
751 d->scrollingSegmentsEndPos(Qt::Vertical));
753 QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin,
754 rect.width() + 2 * xmargin, rect.height() + 2 * ymargin);
756 QSizeF visible = d->viewportSize;
757 QRectF visibleRect(startPos, visible);
759 qScrollerDebug() << "QScroller::ensureVisible(" << rect << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])";
760 qScrollerDebug() << " --> content position:" << d->contentPosition;
762 if (visibleRect.contains(marginRect))
765 QPointF newPos = startPos;
767 if (visibleRect.width() < rect.width()) {
768 // at least try to move the rect into view
769 if (rect.left() > visibleRect.left())
770 newPos.setX(rect.left());
771 else if (rect.right() < visibleRect.right())
772 newPos.setX(rect.right() - visible.width());
774 } else if (visibleRect.width() < marginRect.width()) {
775 newPos.setX(rect.center().x() - visibleRect.width() / 2);
776 } else if (marginRect.left() > visibleRect.left()) {
777 newPos.setX(marginRect.left());
778 } else if (marginRect.right() < visibleRect.right()) {
779 newPos.setX(marginRect.right() - visible.width());
782 if (visibleRect.height() < rect.height()) {
783 // at least try to move the rect into view
784 if (rect.top() > visibleRect.top())
785 newPos.setX(rect.top());
786 else if (rect.bottom() < visibleRect.bottom())
787 newPos.setX(rect.bottom() - visible.height());
789 } else if (visibleRect.height() < marginRect.height()) {
790 newPos.setY(rect.center().y() - visibleRect.height() / 2);
791 } else if (marginRect.top() > visibleRect.top()) {
792 newPos.setY(marginRect.top());
793 } else if (marginRect.bottom() < visibleRect.bottom()) {
794 newPos.setY(marginRect.bottom() - visible.height());
797 // clamp to maximum content position
798 newPos = clampToRect(newPos, d->contentPosRange);
799 if (newPos == startPos)
802 scrollTo(newPos, scrollTime);
805 /*! This function resends the QScrollPrepareEvent.
806 Calling resendPrepareEvent triggers a QScrollPrepareEvent from the scroller.
807 This allows the receiver to re-set content position and content size while
809 Calling this function while in the Inactive state is useless as the prepare event
810 is sent again before scrolling starts.
812 void QScroller::resendPrepareEvent()
815 d->prepareScrolling(d->pressPosition);
818 /*! Set the snap positions for the horizontal axis to a list of \a positions.
819 This overwrites all previously set snap positions and also a previously
820 set snapping interval.
821 Snapping can be deactivated by setting an empty list of positions.
823 void QScroller::setSnapPositionsX(const QList<qreal> &positions)
826 d->snapPositionsX = positions;
827 d->snapIntervalX = 0.0;
829 d->recalcScrollingSegments();
832 /*! Set the snap positions for the horizontal axis to regular spaced intervals.
833 The first snap position is at \a first. The next at \a first + \a interval.
834 This can be used to implement a list header.
835 This overwrites all previously set snap positions and also a previously
836 set snapping interval.
837 Snapping can be deactivated by setting an interval of 0.0
839 void QScroller::setSnapPositionsX(qreal first, qreal interval)
842 d->snapFirstX = first;
843 d->snapIntervalX = interval;
844 d->snapPositionsX.clear();
846 d->recalcScrollingSegments();
849 /*! Set the snap positions for the vertical axis to a list of \a positions.
850 This overwrites all previously set snap positions and also a previously
851 set snapping interval.
852 Snapping can be deactivated by setting an empty list of positions.
854 void QScroller::setSnapPositionsY(const QList<qreal> &positions)
857 d->snapPositionsY = positions;
858 d->snapIntervalY = 0.0;
860 d->recalcScrollingSegments();
863 /*! Set the snap positions for the vertical axis to regular spaced intervals.
864 The first snap position is at \a first. The next at \a first + \a interval.
865 This overwrites all previously set snap positions and also a previously
866 set snapping interval.
867 Snapping can be deactivated by setting an interval of 0.0
869 void QScroller::setSnapPositionsY(qreal first, qreal interval)
872 d->snapFirstY = first;
873 d->snapIntervalY = interval;
874 d->snapPositionsY.clear();
876 d->recalcScrollingSegments();
881 // -------------- private ------------
883 QScrollerPrivate::QScrollerPrivate(QScroller *q, QObject *_target)
885 #ifndef QT_NO_GESTURES
887 , recognizerType(Qt::CustomGesture)
889 , state(QScroller::Inactive)
897 #ifndef QT_NO_ANIMATION
898 , scrollTimer(new QScrollTimer(this))
902 connect(target, SIGNAL(destroyed(QObject*)), this, SLOT(targetDestroyed()));
905 void QScrollerPrivate::init()
908 monotonicTimer.start();
911 void QScrollerPrivate::sendEvent(QObject *o, QEvent *e)
913 qt_sendSpontaneousEvent(o, e);
916 const char *QScrollerPrivate::stateName(QScroller::State state)
919 case QScroller::Inactive: return "inactive";
920 case QScroller::Pressed: return "pressed";
921 case QScroller::Dragging: return "dragging";
922 case QScroller::Scrolling: return "scrolling";
923 default: return "(invalid)";
927 const char *QScrollerPrivate::inputName(QScroller::Input input)
930 case QScroller::InputPress: return "press";
931 case QScroller::InputMove: return "move";
932 case QScroller::InputRelease: return "release";
933 default: return "(invalid)";
937 void QScrollerPrivate::targetDestroyed()
939 #ifndef QT_NO_ANIMATION
945 void QScrollerPrivate::timerTick()
948 QScroller::State state;
949 typedef void (QScrollerPrivate::*timerhandler_t)();
950 timerhandler_t handler;
953 timerevent timerevents[] = {
954 { QScroller::Dragging, &QScrollerPrivate::timerEventWhileDragging },
955 { QScroller::Scrolling, &QScrollerPrivate::timerEventWhileScrolling },
958 for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) {
959 timerevent *te = timerevents + i;
961 if (state == te->state) {
962 (this->*te->handler)();
967 #ifndef QT_NO_ANIMATION
973 This function is used by gesture recognizers to inform the scroller about a new input event.
974 The scroller changes its internal state() according to the input event and its attached
975 scroller properties. The scroller doesn't distinguish between the kind of input device the
976 event came from. Therefore the event needs to be split into the \a input type, a \a position and a
977 milli-second \a timestamp. The \a position needs to be in the target's coordinate system.
979 The return value is \c true if the event should be consumed by the calling filter or \c false
980 if the event should be forwarded to the control.
982 \note Using grabGesture() should be sufficient for most use cases.
984 bool QScroller::handleInput(Input input, const QPointF &position, qint64 timestamp)
988 qScrollerDebug() << "QScroller::handleInput(" << input << ", " << d->stateName(d->state) << ", " << position << ", " << timestamp << ")";
992 typedef bool (QScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp);
993 inputhandler_t handler;
996 statechange statechanges[] = {
997 { QScroller::Inactive, InputPress, &QScrollerPrivate::pressWhileInactive },
998 { QScroller::Pressed, InputMove, &QScrollerPrivate::moveWhilePressed },
999 { QScroller::Pressed, InputRelease, &QScrollerPrivate::releaseWhilePressed },
1000 { QScroller::Dragging, InputMove, &QScrollerPrivate::moveWhileDragging },
1001 { QScroller::Dragging, InputRelease, &QScrollerPrivate::releaseWhileDragging },
1002 { QScroller::Scrolling, InputPress, &QScrollerPrivate::pressWhileScrolling }
1005 for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) {
1006 statechange *sc = statechanges + i;
1008 if (d->state == sc->state && input == sc->input)
1009 return (d->*sc->handler)(position - d->overshootPosition, timestamp);
1014 #if !defined(Q_WS_MAC)
1015 // the Mac version is implemented in qscroller_mac.mm
1017 QPointF QScrollerPrivate::realDpi(int screen)
1019 # ifdef Q_WS_MAEMO_5
1022 // The DPI value is hardcoded to 96 on Maemo5:
1023 // https://projects.maemo.org/bugzilla/show_bug.cgi?id=152525
1024 // This value (260) is only correct for the N900 though, but
1025 // there's no way to get the real DPI at run time.
1026 return QPointF(260, 260);
1028 # elif defined(Q_WS_X11) && !defined(QT_NO_XRANDR)
1029 if (X11 && X11->use_xrandr && X11->ptrXRRSizes && X11->ptrXRRRootToScreen) {
1031 // QDesktopWidget is based on Xinerama screens, which do not always
1032 // correspond to RandR screens: NVidia's TwinView e.g. will show up
1033 // as 2 screens in QDesktopWidget, but libXRandR will only see 1 screen.
1034 // (although with the combined size of the Xinerama screens).
1035 // Additionally, libXrandr will simply crash when calling XRRSizes
1036 // for (the non-existent) screen 1 in this scenario.
1037 Window root = RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen);
1038 int randrscreen = (root != XNone) ? X11->ptrXRRRootToScreen(X11->display, root) : -1;
1040 XRRScreenSize *sizes = X11->ptrXRRSizes(X11->display, randrscreen == -1 ? 0 : randrscreen, &nsizes);
1041 if (nsizes > 0 && sizes && sizes->width && sizes->height && sizes->mwidth && sizes->mheight) {
1042 qScrollerDebug() << "XRandR DPI:" << QPointF(qreal(25.4) * qreal(sizes->width) / qreal(sizes->mwidth),
1043 qreal(25.4) * qreal(sizes->height) / qreal(sizes->mheight));
1044 return QPointF(qreal(25.4) * qreal(sizes->width) / qreal(sizes->mwidth),
1045 qreal(25.4) * qreal(sizes->height) / qreal(sizes->mheight));
1050 QWidget *w = QApplication::desktop()->screen(screen);
1051 return QPointF(w->physicalDpiX(), w->physicalDpiY());
1058 Returns the resolution of the used screen.
1060 QPointF QScrollerPrivate::dpi() const
1062 return pixelPerMeter * qreal(0.0254);
1066 Sets the resolution used for scrolling.
1067 This resolution is only used by the kinetic scroller. If you change this
1068 then the scroller will behave quite different as a lot of the values are
1069 given in physical distances (millimeter).
1071 void QScrollerPrivate::setDpi(const QPointF &dpi)
1073 pixelPerMeter = dpi / qreal(0.0254);
1077 Sets the dpi used for scrolling to the value of the widget.
1079 void QScrollerPrivate::setDpiFromWidget(QWidget *widget)
1081 QDesktopWidget *dw = QApplication::desktop();
1082 setDpi(realDpi(widget ? dw->screenNumber(widget) : dw->primaryScreen()));
1086 Updates the velocity during dragging.
1087 Sets releaseVelocity.
1089 void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime)
1095 QPointF ppm = q->pixelPerMeter();
1096 const QScrollerPropertiesPrivate *sp = properties.d.data();
1097 QPointF deltaPixel = deltaPixelRaw;
1099 qScrollerDebug() << "QScroller::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])";
1101 // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms)
1102 if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5))
1103 deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength();
1105 QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm;
1106 // around 95% of all updates are in the [1..50] ms range, so make sure
1107 // to scale the smoothing factor over that range: this way a 50ms update
1108 // will have full impact, while 5ms update will only have a 10% impact.
1109 qreal smoothing = sp->dragVelocitySmoothingFactor * qMin(qreal(deltaTime), qreal(50)) / qreal(50);
1111 // only smooth if we already have a release velocity and only if the
1112 // user hasn't stopped to move his finger for more than 100ms
1113 if ((releaseVelocity != QPointF(0, 0)) && (deltaTime < 100)) {
1114 qScrollerDebug() << "SMOOTHED from " << newv << " to " << newv * smoothing + releaseVelocity * (qreal(1) - smoothing);
1115 // smooth x or y only if the new velocity is either 0 or at least in
1116 // the same direction of the release velocity
1117 if (!newv.x() || (qSign(releaseVelocity.x()) == qSign(newv.x())))
1118 newv.setX(newv.x() * smoothing + releaseVelocity.x() * (qreal(1) - smoothing));
1119 if (!newv.y() || (qSign(releaseVelocity.y()) == qSign(newv.y())))
1120 newv.setY(newv.y() * smoothing + releaseVelocity.y() * (qreal(1) - smoothing));
1122 qScrollerDebug() << "NO SMOOTHING to " << newv;
1124 releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity));
1125 releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity));
1127 qScrollerDebug() << " --> new velocity:" << releaseVelocity;
1130 void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation)
1132 if (startPos == stopPos || deltaPos == 0)
1136 if (orientation == Qt::Horizontal && !xSegments.isEmpty())
1137 s.startTime = xSegments.last().startTime + xSegments.last().deltaTime * xSegments.last().stopProgress;
1138 else if (orientation == Qt::Vertical && !ySegments.isEmpty())
1139 s.startTime = ySegments.last().startTime + ySegments.last().deltaTime * ySegments.last().stopProgress;
1141 s.startTime = monotonicTimer.elapsed();
1143 s.startPos = startPos;
1144 s.deltaPos = deltaPos;
1145 s.stopPos = stopPos;
1146 s.deltaTime = deltaTime * 1000;
1147 s.stopProgress = stopProgress;
1148 s.curve.setType(curve);
1151 if (orientation == Qt::Horizontal)
1152 xSegments.enqueue(s);
1154 ySegments.enqueue(s);
1156 qScrollerDebug() << "+++ Added a new ScrollSegment: " << s;
1161 Clears the old segments and recalculates them if the current segments are not longer valid
1163 void QScrollerPrivate::recalcScrollingSegments(bool forceRecalc)
1166 QPointF ppm = q->pixelPerMeter();
1168 releaseVelocity = q->velocity();
1170 if (forceRecalc || !scrollingSegmentsValid(Qt::Horizontal))
1171 createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal);
1173 if (forceRecalc || !scrollingSegmentsValid(Qt::Vertical))
1174 createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical);
1178 Returns the end position after the current scroll has finished.
1180 qreal QScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const
1182 if (orientation == Qt::Horizontal) {
1183 if (xSegments.isEmpty())
1184 return contentPosition.x() + overshootPosition.x();
1186 return xSegments.last().stopPos;
1188 if (ySegments.isEmpty())
1189 return contentPosition.y() + overshootPosition.y();
1191 return ySegments.last().stopPos;
1196 Checks if the scroller segment end in a valid position.
1198 bool QScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation)
1200 QQueue<ScrollSegment> *segments;
1204 if (orientation == Qt::Horizontal) {
1205 segments = &xSegments;
1206 minPos = contentPosRange.left();
1207 maxPos = contentPosRange.right();
1209 segments = &ySegments;
1210 minPos = contentPosRange.top();
1211 maxPos = contentPosRange.bottom();
1214 if (segments->isEmpty())
1217 const ScrollSegment &last = segments->last();
1218 qreal stopPos = last.stopPos;
1220 if (last.type == ScrollTypeScrollTo)
1221 return true; // scrollTo is always valid
1223 if (last.type == ScrollTypeOvershoot &&
1224 (stopPos != minPos && stopPos != maxPos))
1227 if (stopPos < minPos || stopPos > maxPos)
1230 if (stopPos == minPos || stopPos == maxPos) // the begin and the end of the list are always ok
1233 qreal nextSnap = nextSnapPos(stopPos, 0, orientation);
1234 if (!qIsNaN(nextSnap) && stopPos != nextSnap)
1241 Creates the sections needed to scroll to the specific \a endPos to the segments queue.
1243 void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type)
1247 if (orientation == Qt::Horizontal)
1252 qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation);
1254 const QScrollerPropertiesPrivate *sp = properties.d.data();
1256 qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x()
1257 : contentPosition.y() + overshootPosition.y();
1258 qreal deltaPos = (endPos - startPos) / 2;
1260 pushSegment(type, deltaTime * qreal(0.3), qreal(1.0), startPos, deltaPos, startPos + deltaPos, QEasingCurve::InQuad, orientation);
1261 pushSegment(type, deltaTime * qreal(0.7), qreal(1.0), startPos + deltaPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
1266 void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation)
1268 const QScrollerPropertiesPrivate *sp = properties.d.data();
1270 QScrollerProperties::OvershootPolicy policy;
1275 if (orientation == Qt::Horizontal) {
1277 policy = sp->hOvershootPolicy;
1278 minPos = contentPosRange.left();
1279 maxPos = contentPosRange.right();
1280 viewSize = viewportSize.width();
1283 policy = sp->vOvershootPolicy;
1284 minPos = contentPosRange.top();
1285 maxPos = contentPosRange.bottom();
1286 viewSize = viewportSize.height();
1289 bool alwaysOvershoot = (policy == QScrollerProperties::OvershootAlwaysOn);
1290 bool noOvershoot = (policy == QScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor;
1291 bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos);
1293 qScrollerDebug() << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos << "o:" << int(orientation);
1295 qScrollerDebug() << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor << ", curveType = " << sp->scrollingCurve.type();
1297 // This is only correct for QEasingCurve::OutQuad (linear velocity,
1298 // constant deceleration), but the results look and feel ok for OutExpo
1299 // and OutSine as well
1301 // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime)
1304 // deltaTime = (2 * vrelease) / (a * differntial(0))
1306 // pos(t) = integrate(v(t)dt)
1307 // pos(t) = vrelease * t - 0.5 * a * t * t
1308 // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime
1309 // deltaPos = pos(deltaTime)
1311 qreal deltaTime = (qreal(2) * qAbs(v)) / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0));
1312 qreal deltaPos = qSign(v) * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor * ppm;
1313 qreal endPos = startPos + deltaPos;
1315 qScrollerDebug() << " Real Delta:" << deltaPos;
1317 // -- determine snap points
1318 qreal nextSnap = nextSnapPos(endPos, 0, orientation);
1319 qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation);
1320 qreal higherSnapPos = nextSnapPos(startPos, 1, orientation);
1322 qScrollerDebug() << " Real Delta:" << lowerSnapPos <<"-"<<nextSnap <<"-"<<higherSnapPos;
1324 // - check if we can reach another snap point
1325 if (nextSnap > higherSnapPos || qIsNaN(higherSnapPos))
1326 higherSnapPos = nextSnap;
1327 if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos))
1328 lowerSnapPos = nextSnap;
1330 // -- check if are in overshoot and end in overshoot
1331 if ((startPos < minPos && endPos < minPos) ||
1332 (startPos > maxPos && endPos > maxPos)) {
1333 qreal stopPos = endPos < minPos ? minPos : maxPos;
1334 qreal oDeltaTime = sp->overshootScrollTime;
1336 pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), startPos, stopPos - startPos, stopPos, sp->scrollingCurve.type(), orientation);
1340 if (qAbs(v) < sp->minimumVelocity) {
1342 qScrollerDebug() << "### below minimum Vel" << orientation;
1344 // - no snap points or already at one
1345 if (qIsNaN(nextSnap) || nextSnap == startPos)
1346 return; // nothing to do, no scrolling needed.
1348 // - decide which point to use
1350 qreal snapDistance = higherSnapPos - lowerSnapPos;
1352 qreal pressDistance = (orientation == Qt::Horizontal) ?
1353 lastPosition.x() - pressPosition.x() :
1354 lastPosition.y() - pressPosition.y();
1356 // if not dragged far enough, pick the next snap point.
1357 if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance)
1359 else if (pressDistance < 0.0)
1360 endPos = lowerSnapPos;
1362 endPos = higherSnapPos;
1364 deltaPos = endPos - startPos;
1365 qreal midPos = startPos + deltaPos * qreal(0.3);
1366 pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.3), qreal(1.0), startPos, midPos - startPos, midPos, QEasingCurve::InQuad, orientation);
1367 pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.7), qreal(1.0), midPos, endPos - midPos, endPos, sp->scrollingCurve.type(), orientation);
1371 // - go to the next snappoint if there is one
1372 if (v > 0 && !qIsNaN(higherSnapPos)) {
1373 // change the time in relation to the changed end position
1374 if (endPos - startPos)
1375 deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos));
1376 if (deltaTime > sp->snapTime)
1377 deltaTime = sp->snapTime;
1378 endPos = higherSnapPos;
1380 } else if (v < 0 && !qIsNaN(lowerSnapPos)) {
1381 // change the time in relation to the changed end position
1382 if (endPos - startPos)
1383 deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos));
1384 if (deltaTime > sp->snapTime)
1385 deltaTime = sp->snapTime;
1386 endPos = lowerSnapPos;
1388 // -- check if we are overshooting
1389 } else if (endPos < minPos || endPos > maxPos) {
1390 qreal stopPos = endPos < minPos ? minPos : maxPos;
1392 qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos);
1394 qreal stopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos));
1396 if (!canOvershoot) {
1397 qScrollerDebug() << "Overshoot stopp:" << stopProgress;
1399 pushSegment(ScrollTypeFlick, deltaTime, stopProgress, startPos, endPos, stopPos, sp->scrollingCurve.type(), orientation);
1401 qreal oDeltaTime = sp->overshootScrollTime;
1402 qreal oStopProgress = qMin(stopProgress + oDeltaTime * qreal(0.3) / deltaTime, qreal(1));
1403 qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(oStopProgress) - stopPos;
1404 qreal oMaxDistance = qSign(oDistance) * (viewSize * sp->overshootScrollDistanceFactor);
1406 qScrollerDebug() << "1 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
1408 if (qAbs(oDistance) > qAbs(oMaxDistance)) {
1409 oStopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos + oMaxDistance - startPos) / deltaPos));
1410 oDistance = oMaxDistance;
1411 qScrollerDebug() << "2 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
1414 pushSegment(ScrollTypeFlick, deltaTime, oStopProgress, startPos, deltaPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation);
1415 pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), stopPos + oDistance, -oDistance, stopPos, sp->scrollingCurve.type(), orientation);
1420 pushSegment(ScrollTypeFlick, deltaTime, qreal(1.0), startPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
1425 Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget.
1426 Returns true if the scrolling was accepted and a target was returned.
1428 bool QScrollerPrivate::prepareScrolling(const QPointF &position)
1430 QScrollPrepareEvent spe(position);
1432 sendEvent(target, &spe);
1434 qScrollerDebug() << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted() << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos();
1435 if (spe.isAccepted()) {
1436 QPointF oldContentPos = contentPosition + overshootPosition;
1437 QPointF contentDelta = spe.contentPos() - oldContentPos;
1439 viewportSize = spe.viewportSize();
1440 contentPosRange = spe.contentPosRange();
1441 if (contentPosRange.width() < 0)
1442 contentPosRange.setWidth(0);
1443 if (contentPosRange.height() < 0)
1444 contentPosRange.setHeight(0);
1445 contentPosition = clampToRect(spe.contentPos(), contentPosRange);
1446 overshootPosition = spe.contentPos() - contentPosition;
1448 // - check if the content position was moved
1449 if (contentDelta != QPointF(0, 0)) {
1450 // need to correct all segments
1451 for (int i = 0; i < xSegments.count(); i++)
1452 xSegments[i].startPos -= contentDelta.x();
1454 for (int i = 0; i < ySegments.count(); i++)
1455 ySegments[i].startPos -= contentDelta.y();
1458 if (QWidget *w = qobject_cast<QWidget *>(target))
1459 setDpiFromWidget(w);
1460 #ifndef QT_NO_GRAPHICSVIEW
1461 if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(target)) {
1462 //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
1463 if (go->scene() && !go->scene()->views().isEmpty())
1464 setDpiFromWidget(go->scene()->views().first());
1468 if (state == QScroller::Scrolling) {
1469 recalcScrollingSegments();
1477 void QScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp)
1479 const QScrollerPropertiesPrivate *sp = properties.d.data();
1481 QPointF deltaPixel = position - lastPosition;
1482 qint64 deltaTime = timestamp - lastTimestamp;
1484 if (sp->axisLockThreshold) {
1485 int dx = qAbs(deltaPixel.x());
1486 int dy = qAbs(deltaPixel.y());
1488 bool vertical = (dy > dx);
1489 qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
1490 //qScrollerDebug() << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy;
1491 if (alpha <= sp->axisLockThreshold) {
1500 // calculate velocity (if the user would release the mouse NOW)
1501 updateVelocity(deltaPixel, deltaTime);
1503 // restrict velocity, if content is not scrollable
1504 QRectF max = contentPosRange;
1505 bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1506 bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1510 releaseVelocity.setX(0);
1514 releaseVelocity.setY(0);
1518 // // Do not delay the first drag
1519 // setContentPositionHelper(q->contentPosition() - overshootDistance - deltaPixel);
1520 // dragDistance = QPointF(0, 0);
1522 dragDistance += deltaPixel;
1524 //qScrollerDebug() << "######################" << deltaPixel << position.y() << lastPosition.y();
1526 lastPosition.setX(position.x());
1528 lastPosition.setY(position.y());
1529 lastTimestamp = timestamp;
1532 bool QScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp)
1534 if (prepareScrolling(position)) {
1535 const QScrollerPropertiesPrivate *sp = properties.d.data();
1537 if (!contentPosRange.isNull() ||
1538 (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) ||
1539 (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) {
1541 lastPosition = pressPosition = position;
1542 lastTimestamp = pressTimestamp = timestamp;
1543 setState(QScroller::Pressed);
1549 bool QScrollerPrivate::releaseWhilePressed(const QPointF &, qint64)
1551 if (overshootPosition != QPointF(0.0, 0.0)) {
1552 setState(QScroller::Scrolling);
1555 setState(QScroller::Inactive);
1560 bool QScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp)
1563 const QScrollerPropertiesPrivate *sp = properties.d.data();
1564 QPointF ppm = q->pixelPerMeter();
1566 QPointF deltaPixel = position - pressPosition;
1568 bool moveAborted = false;
1569 bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance);
1571 // check the direction of the mouse drag and abort if it's too much in the wrong direction.
1573 QRectF max = contentPosRange;
1574 bool canScrollX = (max.width() > 0);
1575 bool canScrollY = (max.height() > 0);
1577 if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1579 if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1582 if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) {
1592 setState(QScroller::Inactive);
1593 moveStarted = false;
1595 } else if (moveStarted) {
1596 setState(QScroller::Dragging);
1598 // subtract the dragStartDistance
1599 deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength());
1601 if (deltaPixel != QPointF(0, 0)) {
1602 // handleDrag updates lastPosition, lastTimestamp and velocity
1603 handleDrag(pressPosition + deltaPixel, timestamp);
1609 bool QScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp)
1611 // handleDrag updates lastPosition, lastTimestamp and velocity
1612 handleDrag(position, timestamp);
1616 void QScrollerPrivate::timerEventWhileDragging()
1618 if (dragDistance != QPointF(0, 0)) {
1619 qScrollerDebug() << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance;
1621 setContentPositionHelperDragging(-dragDistance);
1622 dragDistance = QPointF(0, 0);
1626 bool QScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp)
1629 const QScrollerPropertiesPrivate *sp = properties.d.data();
1631 // handleDrag updates lastPosition, lastTimestamp and velocity
1632 handleDrag(position, timestamp);
1634 // check if we moved at all - this can happen if you stop a running
1635 // scroller with a press and release shortly afterwards
1636 QPointF deltaPixel = position - pressPosition;
1637 if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) {
1639 // handle accelerating flicks
1640 if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime &&
1641 ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) {
1643 // - determine if the direction was changed
1644 int signX = 0, signY = 0;
1645 if (releaseVelocity.x())
1646 signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1;
1647 if (releaseVelocity.y())
1648 signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1;
1651 releaseVelocity.setX(qBound(-sp->maximumVelocity,
1652 oldVelocity.x() * sp->acceleratingFlickSpeedupFactor,
1653 sp->maximumVelocity));
1655 releaseVelocity.setY(qBound(-sp->maximumVelocity,
1656 oldVelocity.y() * sp->acceleratingFlickSpeedupFactor,
1657 sp->maximumVelocity));
1661 QPointF ppm = q->pixelPerMeter();
1662 createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal);
1663 createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical);
1665 qScrollerDebug() << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition;
1667 if (xSegments.isEmpty() && ySegments.isEmpty())
1668 setState(QScroller::Inactive);
1670 setState(QScroller::Scrolling);
1675 void QScrollerPrivate::timerEventWhileScrolling()
1677 qScrollerDebug() << "QScroller::timerEventWhileScrolling()";
1679 setContentPositionHelperScrolling();
1680 if (xSegments.isEmpty() && ySegments.isEmpty())
1681 setState(QScroller::Inactive);
1684 bool QScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp)
1688 if ((q->velocity() <= properties.d->maximumClickThroughVelocity) &&
1689 (overshootPosition == QPointF(0.0, 0.0))) {
1690 setState(QScroller::Inactive);
1693 lastPosition = pressPosition = position;
1694 lastTimestamp = pressTimestamp = timestamp;
1695 setState(QScroller::Pressed);
1696 setState(QScroller::Dragging);
1702 This function handles all state changes of the scroller.
1704 void QScrollerPrivate::setState(QScroller::State newstate)
1707 bool sendLastScroll = false;
1709 if (state == newstate)
1712 qScrollerDebug() << q << "QScroller::setState(" << stateName(newstate) << ")";
1715 case QScroller::Inactive:
1716 #ifndef QT_NO_ANIMATION
1717 scrollTimer->stop();
1720 // send the last scroll event (but only after the current state change was finished)
1722 sendLastScroll = true;
1724 releaseVelocity = QPointF(0, 0);
1727 case QScroller::Pressed:
1728 #ifndef QT_NO_ANIMATION
1729 scrollTimer->stop();
1732 oldVelocity = releaseVelocity;
1733 releaseVelocity = QPointF(0, 0);
1736 case QScroller::Dragging:
1737 dragDistance = QPointF(0, 0);
1738 #ifndef QT_NO_ANIMATION
1739 if (state == QScroller::Pressed)
1740 scrollTimer->start();
1744 case QScroller::Scrolling:
1745 #ifndef QT_NO_ANIMATION
1746 scrollTimer->start();
1751 qSwap(state, newstate);
1753 if (sendLastScroll) {
1754 QScrollEvent se(contentPosition, overshootPosition, QScrollEvent::ScrollFinished);
1755 sendEvent(target, &se);
1758 if (state == QScroller::Dragging || state == QScroller::Scrolling)
1759 qt_activeScrollers()->insert(q);
1761 qt_activeScrollers()->remove(q);
1762 emit q->stateChanged(state);
1767 Helps when setting the content position.
1768 It will try to move the content by the requested delta but stop in case
1769 when we are coming back from an overshoot or a scrollTo.
1770 It will also indicate a new overshooting condition by the overshootX and oversthootY flags.
1772 In this cases it will reset the velocity variables and other flags.
1774 Also keeps track of the current over-shooting value in overshootPosition.
1776 \a deltaPos is the amount of pixels the current content position should be moved
1778 void QScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos)
1780 const QScrollerPropertiesPrivate *sp = properties.d.data();
1782 if (sp->overshootDragResistanceFactor)
1783 overshootPosition /= sp->overshootDragResistanceFactor;
1785 QPointF oldPos = contentPosition + overshootPosition;
1786 QPointF newPos = oldPos + deltaPos;
1788 qScrollerDebug() << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])";
1789 qScrollerDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos;
1791 QPointF oldClampedPos = clampToRect(oldPos, contentPosRange);
1792 QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1794 // --- handle overshooting and stop if the coordinate is going back inside the normal area
1795 bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1796 bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1797 bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1798 ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1799 !sp->overshootDragDistanceFactor;
1800 bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1801 ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1802 !sp->overshootDragDistanceFactor;
1803 bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width());
1804 bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height());
1806 qreal oldOvershootX = (canOvershootX) ? oldPos.x() - oldClampedPos.x() : 0;
1807 qreal oldOvershootY = (canOvershootY) ? oldPos.y() - oldClampedPos.y() : 0;
1809 qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0;
1810 qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0;
1812 qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor;
1813 qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor;
1815 qScrollerDebug() << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy;
1816 qScrollerDebug() << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX;
1818 if (sp->overshootDragResistanceFactor) {
1819 oldOvershootX *= sp->overshootDragResistanceFactor;
1820 oldOvershootY *= sp->overshootDragResistanceFactor;
1821 newOvershootX *= sp->overshootDragResistanceFactor;
1822 newOvershootY *= sp->overshootDragResistanceFactor;
1825 // -- stop at the maximum overshoot distance
1827 newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX);
1828 newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY);
1830 overshootPosition.setX(newOvershootX);
1831 overshootPosition.setY(newOvershootY);
1832 contentPosition = newClampedPos;
1834 QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
1835 sendEvent(target, &se);
1836 firstScroll = false;
1838 qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition <<
1839 "- overshoot x/y?:" << overshootPosition;
1843 qreal QScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos)
1847 // check the X segments for new positions
1848 while (!segments.isEmpty()) {
1849 const ScrollSegment s = segments.head();
1851 if ((s.startTime + s.deltaTime * s.stopProgress) <= now) {
1854 } else if (s.startTime <= now) {
1855 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
1856 pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress);
1857 if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) {
1870 void QScrollerPrivate::setContentPositionHelperScrolling()
1872 qint64 now = monotonicTimer.elapsed();
1873 QPointF newPos = contentPosition + overshootPosition;
1875 newPos.setX(nextSegmentPosition(xSegments, now, newPos.x()));
1876 newPos.setY(nextSegmentPosition(ySegments, now, newPos.y()));
1878 // -- set the position and handle overshoot
1879 qScrollerDebug() << "QScroller::setContentPositionHelperScrolling()";
1880 qScrollerDebug() << " --> overshoot:" << overshootPosition << "- new pos:" << newPos;
1882 QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1884 overshootPosition = newPos - newClampedPos;
1885 contentPosition = newClampedPos;
1887 QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
1888 sendEvent(target, &se);
1889 firstScroll = false;
1891 qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition;
1895 Returns the next snap point in direction.
1896 If \a direction >0 it will return the next snap point that is larger than the current position.
1897 If \a direction <0 it will return the next snap point that is smaller than the current position.
1898 If \a direction ==0 it will return the nearest snap point (or the current position if we are already
1900 Returns the nearest snap position or NaN if no such point could be found.
1902 qreal QScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation)
1904 qreal bestSnapPos = Q_QNAN;
1905 qreal bestSnapPosDist = Q_INFINITY;
1910 if (orientation == Qt::Horizontal) {
1911 minPos = contentPosRange.left();
1912 maxPos = contentPosRange.right();
1914 minPos = contentPosRange.top();
1915 maxPos = contentPosRange.bottom();
1918 if (orientation == Qt::Horizontal) {
1919 // the snap points in the list
1920 foreach (qreal snapPos, snapPositionsX) {
1921 qreal snapPosDist = snapPos - p;
1922 if ((dir > 0 && snapPosDist < 0) ||
1923 (dir < 0 && snapPosDist > 0))
1924 continue; // wrong direction
1925 if (snapPos < minPos || snapPos > maxPos )
1926 continue; // invalid
1928 if (qIsNaN(bestSnapPos) ||
1929 qAbs(snapPosDist) < bestSnapPosDist ) {
1930 bestSnapPos = snapPos;
1931 bestSnapPosDist = qAbs(snapPosDist);
1935 // the snap point interval
1936 if (snapIntervalX > 0.0) {
1937 qreal first = minPos + snapFirstX;
1940 snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first;
1942 snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first;
1943 else if (p <= first)
1947 qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first;
1951 snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first;
1954 if (snapPos >= first && snapPos <= maxPos ) {
1955 qreal snapPosDist = snapPos - p;
1957 if (qIsNaN(bestSnapPos) ||
1958 qAbs(snapPosDist) < bestSnapPosDist ) {
1959 bestSnapPos = snapPos;
1960 bestSnapPosDist = qAbs(snapPosDist);
1965 } else { // (orientation == Qt::Vertical)
1966 // the snap points in the list
1967 foreach (qreal snapPos, snapPositionsY) {
1968 qreal snapPosDist = snapPos - p;
1969 if ((dir > 0 && snapPosDist < 0) ||
1970 (dir < 0 && snapPosDist > 0))
1971 continue; // wrong direction
1972 if (snapPos < minPos || snapPos > maxPos )
1973 continue; // invalid
1975 if (qIsNaN(bestSnapPos) ||
1976 qAbs(snapPosDist) < bestSnapPosDist) {
1977 bestSnapPos = snapPos;
1978 bestSnapPosDist = qAbs(snapPosDist);
1982 // the snap point interval
1983 if (snapIntervalY > 0.0) {
1984 qreal first = minPos + snapFirstY;
1987 snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first;
1989 snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first;
1990 else if (p <= first)
1994 qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first;
1998 snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first;
2001 if (snapPos >= first && snapPos <= maxPos ) {
2002 qreal snapPosDist = snapPos - p;
2004 if (qIsNaN(bestSnapPos) ||
2005 qAbs(snapPosDist) < bestSnapPosDist) {
2006 bestSnapPos = snapPos;
2007 bestSnapPosDist = qAbs(snapPosDist);
2017 \enum QScroller::State
2019 This enum contains the different QScroller states.
2021 \value Inactive The scroller is not scrolling and nothing is pressed.
2022 \value Pressed A touch event was received or the mouse button was pressed but the scroll area is currently not dragged.
2023 \value Dragging The scroll area is currently following the touch point or mouse.
2024 \value Scrolling The scroll area is moving on it's own.
2028 \enum QScroller::ScrollerGestureType
2030 This enum contains the different gesture types that are supported by the QScroller gesture recognizer.
2032 \value TouchGesture The gesture recognizer will only trigger on touch
2033 events. Specifically it will react on single touch points when using a
2034 touch screen and dual touch points when using a touchpad.
2035 \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events.
2036 \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events.
2037 \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events.
2041 \enum QScroller::Input
2043 This enum contains an input device agnostic view of input events that are relevant for QScroller.
2045 \value InputPress The user pressed the input device (e.g. QEvent::MouseButtonPress,
2046 QEvent::GraphicsSceneMousePress, QEvent::TouchBegin)
2048 \value InputMove The user moved the input device (e.g. QEvent::MouseMove,
2049 QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate)
2051 \value InputRelease The user released the input device (e.g. QEvent::MouseButtonRelease,
2052 QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd)