1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtWidgets module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qevent.h"
41 #include "qwidget.h"
42 #include "qscroller.h"
43 #include "private/qflickgesture_p.h"
44 #include "private/qscroller_p.h"
45 #include "qscrollerproperties.h"
46 #include "private/qscrollerproperties_p.h"
47 #include "qnumeric.h"
48 #include "math.h"
49 
50 #include <QTime>
51 #include <QElapsedTimer>
52 #include <QMap>
53 #include <QApplication>
54 #include <QAbstractScrollArea>
55 #if QT_CONFIG(graphicsview)
56 #include <QGraphicsObject>
57 #include <QGraphicsScene>
58 #include <QGraphicsView>
59 #endif
60 #include <QDesktopWidget>
61 #include <private/qdesktopwidget_p.h>
62 #include <QVector2D>
63 #include <QtCore/qmath.h>
64 #include <QtGui/qevent.h>
65 #include <qnumeric.h>
66 
67 #include <QtDebug>
68 
69 
70 
71 QT_BEGIN_NAMESPACE
72 
73 bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
74 
75 //#define QSCROLLER_DEBUG
76 
77 #ifdef QSCROLLER_DEBUG
78 #  define qScrollerDebug  qDebug
79 #else
80 #  define qScrollerDebug  while (false) qDebug
81 #endif
82 
operator <<(QDebug & dbg,const QScrollerPrivate::ScrollSegment & s)83 QDebug &operator<<(QDebug &dbg, const QScrollerPrivate::ScrollSegment &s)
84 {
85     dbg << "\n  Time: start:" << s.startTime << " duration:" << s.deltaTime << " stop progress:" << s.stopProgress;
86     dbg << "\n  Pos: start:" << s.startPos << " delta:" << s.deltaPos << " stop:" << s.stopPos;
87     dbg << "\n  Curve: type:" << s.curve.type() << "\n";
88     return dbg;
89 }
90 
91 
92 // a few helper operators to make the code below a lot more readable:
93 // otherwise a lot of ifs would have to be multi-line to check both the x
94 // and y coordinate separately.
95 
96 // returns true only if the abs. value of BOTH x and y are <= f
operator <=(const QPointF & p,qreal f)97 inline bool operator<=(const QPointF &p, qreal f)
98 {
99     return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f);
100 }
101 
102 // returns true only if the abs. value of BOTH x and y are < f
operator <(const QPointF & p,qreal f)103 inline bool operator<(const QPointF &p, qreal f)
104 {
105     return (qAbs(p.x()) < f) && (qAbs(p.y()) < f);
106 }
107 
108 // returns true if the abs. value of EITHER x or y are >= f
operator >=(const QPointF & p,qreal f)109 inline bool operator>=(const QPointF &p, qreal f)
110 {
111     return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f);
112 }
113 
114 // returns true if the abs. value of EITHER x or y are > f
operator >(const QPointF & p,qreal f)115 inline bool operator>(const QPointF &p, qreal f)
116 {
117     return (qAbs(p.x()) > f) || (qAbs(p.y()) > f);
118 }
119 
120 // returns a new point with both coordinates having the abs. value of the original one
qAbs(const QPointF & p)121 inline QPointF qAbs(const QPointF &p)
122 {
123     return QPointF(qAbs(p.x()), qAbs(p.y()));
124 }
125 
126 // returns a new point with all components of p1 multiplied by the corresponding components of p2
operator *(const QPointF & p1,const QPointF & p2)127 inline QPointF operator*(const QPointF &p1, const QPointF &p2)
128 {
129     return QPointF(p1.x() * p2.x(), p1.y() * p2.y());
130 }
131 
132 // returns a new point with all components of p1 divided by the corresponding components of p2
operator /(const QPointF & p1,const QPointF & p2)133 inline QPointF operator/(const QPointF &p1, const QPointF &p2)
134 {
135     return QPointF(p1.x() / p2.x(), p1.y() / p2.y());
136 }
137 
clampToRect(const QPointF & p,const QRectF & rect)138 inline QPointF clampToRect(const QPointF &p, const QRectF &rect)
139 {
140     qreal x = qBound(rect.left(), p.x(), rect.right());
141     qreal y = qBound(rect.top(), p.y(), rect.bottom());
142     return QPointF(x, y);
143 }
144 
145 // returns -1, 0 or +1 according to r being <0, ==0 or >0
qSign(qreal r)146 inline int qSign(qreal r)
147 {
148     return (r < 0) ? -1 : ((r > 0) ? 1 : 0);
149 }
150 
151 // this version is not mathematically exact, but it just works for every
152 // easing curve type (even custom ones)
153 
differentialForProgress(const QEasingCurve & curve,qreal pos)154 static qreal differentialForProgress(const QEasingCurve &curve, qreal pos)
155 {
156     const qreal dx = 0.01;
157     qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx);
158     qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx);
159     qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx);
160 
161     //qScrollerDebug() << "differentialForProgress(type: " << curve.type() << ", pos: " << pos << ") = " << d;
162 
163     return d;
164 }
165 
166 // this version is not mathematically exact, but it just works for every
167 // easing curve type (even custom ones)
168 
progressForValue(const QEasingCurve & curve,qreal value)169 static qreal progressForValue(const QEasingCurve &curve, qreal value)
170 {
171     if (Q_UNLIKELY(curve.type() >= QEasingCurve::InElastic &&
172                    curve.type() < QEasingCurve::Custom)) {
173         qWarning("progressForValue(): QEasingCurves of type %d do not have an inverse, since they are not injective.", curve.type());
174         return value;
175     }
176     if (value < qreal(0) || value > qreal(1))
177         return value;
178 
179     qreal progress = value, left(0), right(1);
180     for (int iterations = 6; iterations; --iterations) {
181         qreal v = curve.valueForProgress(progress);
182         if (v < value)
183             left = progress;
184         else if (v > value)
185             right = progress;
186         else
187             break;
188         progress = (left + right) / qreal(2);
189     }
190     return progress;
191 }
192 
193 
194 #if QT_CONFIG(animation)
195 class QScrollTimer : public QAbstractAnimation
196 {
197 public:
QScrollTimer(QScrollerPrivate * _d)198     QScrollTimer(QScrollerPrivate *_d)
199         : QAbstractAnimation(_d), d(_d), ignoreUpdate(false), skip(0)
200     { }
201 
duration() const202     int duration() const override
203     {
204         return -1;
205     }
206 
start()207     void start()
208     {
209         // QAbstractAnimation::start() will immediately call
210         // updateCurrentTime(), but our state is not set correctly yet
211         ignoreUpdate = true;
212         QAbstractAnimation::start();
213         ignoreUpdate = false;
214         skip = 0;
215     }
216 
217 protected:
updateCurrentTime(int)218     void updateCurrentTime(int /*currentTime*/) override
219    {
220         if (!ignoreUpdate) {
221             if (++skip >= d->frameRateSkip()) {
222                 skip = 0;
223                 d->timerTick();
224             }
225         }
226     }
227 
228 private:
229     QScrollerPrivate *d;
230     bool ignoreUpdate;
231     int skip;
232 };
233 #endif // animation
234 
235 /*!
236     \class QScroller
237     \brief The QScroller class enables kinetic scrolling for any scrolling widget or graphics item.
238     \since 5.0
239 
240     \inmodule QtWidgets
241 
242     With kinetic scrolling, the user can push the widget in a given
243     direction and it will continue to scroll in this direction until it is
244     stopped either by the user or by friction.  Aspects of inertia, friction
245     and other physical concepts can be changed in order to fine-tune an
246     intuitive user experience.
247 
248     The QScroller object is the object that stores the current position and
249     scrolling speed and takes care of updates.
250     QScroller can be triggered by a flick gesture
251 
252     \snippet code/src_widgets_util_qscroller.cpp 0
253 
254     or directly like this:
255 
256     \snippet code/src_widgets_util_qscroller.cpp 1
257 
258     The scrolled QObjects receive a QScrollPrepareEvent whenever the scroller needs to
259     update its geometry information and a QScrollEvent whenever the content of the object should
260     actually be scrolled.
261 
262     The scroller uses the global QAbstractAnimation timer to generate its QScrollEvents. This
263     can be changed with QScrollerProperties::FrameRate on a per-QScroller basis.
264 
265     The \l {Dir View Example} shows one way to use a QScroller with a QTreeView.
266     An example in the \c scroller examples directory also demonstrates QScroller.
267 
268     Even though this kinetic scroller has a large number of settings available via
269     QScrollerProperties, we recommend that you leave them all at their default, platform optimized
270     values. Before changing them you can experiment with the \c plot example in
271     the \c scroller examples directory.
272 
273     \sa QScrollEvent, QScrollPrepareEvent, QScrollerProperties
274 */
275 
276 typedef QMap<QObject *, QScroller *> ScrollerHash;
277 
Q_GLOBAL_STATIC(ScrollerHash,qt_allScrollers)278 Q_GLOBAL_STATIC(ScrollerHash, qt_allScrollers)
279 Q_GLOBAL_STATIC(QList<QScroller *>, qt_activeScrollers)
280 
281 /*!
282     Returns \c true if a QScroller object was already created for \a target; \c false otherwise.
283 
284     \sa scroller()
285 */
286 bool QScroller::hasScroller(QObject *target)
287 {
288     return (qt_allScrollers()->value(target));
289 }
290 
291 /*!
292     Returns the scroller for the given \a target.
293     As long as the object exists this function will always return the same QScroller instance.
294     If no QScroller exists for the \a target, one will implicitly be created.
295     At no point more than one QScroller will be active on an object.
296 
297     \sa hasScroller(), target()
298 */
scroller(QObject * target)299 QScroller *QScroller::scroller(QObject *target)
300 {
301     if (!target) {
302         qWarning("QScroller::scroller() was called with a null target.");
303         return nullptr;
304     }
305 
306     if (qt_allScrollers()->contains(target))
307         return qt_allScrollers()->value(target);
308 
309     QScroller *s = new QScroller(target);
310     qt_allScrollers()->insert(target, s);
311     return s;
312 }
313 
314 /*!
315     \overload
316     This is the const version of scroller().
317 */
scroller(const QObject * target)318 const QScroller *QScroller::scroller(const QObject *target)
319 {
320     return scroller(const_cast<QObject*>(target));
321 }
322 
323 /*!
324     Returns an application wide list of currently active QScroller objects.
325     Active QScroller objects are in a state() that is not QScroller::Inactive.
326     This function is useful when writing your own gesture recognizer.
327 */
activeScrollers()328 QList<QScroller *> QScroller::activeScrollers()
329 {
330     return *qt_activeScrollers();
331 }
332 
333 /*!
334     Returns the target object of this scroller.
335     \sa hasScroller(), scroller()
336  */
target() const337 QObject *QScroller::target() const
338 {
339     Q_D(const QScroller);
340     return d->target;
341 }
342 
343 /*!
344     \fn void QScroller::scrollerPropertiesChanged(const QScrollerProperties &newProperties);
345 
346     QScroller emits this signal whenever its scroller properties change.
347     \a newProperties are the new scroller properties.
348 
349     \sa scrollerProperties
350 */
351 
352 
353 /*! \property QScroller::scrollerProperties
354     \brief The scroller properties of this scroller.
355     The properties are used by the QScroller to determine its scrolling behavior.
356 */
scrollerProperties() const357 QScrollerProperties QScroller::scrollerProperties() const
358 {
359     Q_D(const QScroller);
360     return d->properties;
361 }
362 
setScrollerProperties(const QScrollerProperties & sp)363 void QScroller::setScrollerProperties(const QScrollerProperties &sp)
364 {
365     Q_D(QScroller);
366     if (d->properties != sp) {
367         d->properties = sp;
368         emit scrollerPropertiesChanged(sp);
369 
370         // we need to force the recalculation here, since the overshootPolicy may have changed and
371         // existing segments may include an overshoot animation.
372         d->recalcScrollingSegments(true);
373     }
374 }
375 
376 #ifndef QT_NO_GESTURES
377 
378 /*!
379     Registers a custom scroll gesture recognizer, grabs it for the \a
380     target and returns the resulting gesture type.  If \a scrollGestureType is
381     set to TouchGesture the gesture triggers on touch events. If it is set to
382     one of LeftMouseButtonGesture, RightMouseButtonGesture or
383     MiddleMouseButtonGesture it triggers on mouse events of the
384     corresponding button.
385 
386     Only one scroll gesture can be active on a single object at the same
387     time. If you call this function twice on the same object, it will
388     ungrab the existing gesture before grabbing the new one.
389 
390     \note To avoid unwanted side-effects, mouse events are consumed while
391     the gesture is triggered. Since the initial mouse press event is
392     not consumed, the gesture sends a fake mouse release event
393     at the global position \c{(INT_MIN, INT_MIN)}. This ensures that
394     internal states of the widget that received the original mouse press
395     are consistent.
396 
397     \sa ungrabGesture(), grabbedGesture()
398 */
grabGesture(QObject * target,ScrollerGestureType scrollGestureType)399 Qt::GestureType QScroller::grabGesture(QObject *target, ScrollerGestureType scrollGestureType)
400 {
401     // ensure that a scroller for target is created
402     QScroller *s = scroller(target);
403     if (!s)
404         return Qt::GestureType(0);
405 
406     QScrollerPrivate *sp = s->d_ptr;
407     if (sp->recognizer)
408         ungrabGesture(target); // ungrab the old gesture
409 
410     Qt::MouseButton button;
411     switch (scrollGestureType) {
412     case LeftMouseButtonGesture  : button = Qt::LeftButton; break;
413     case RightMouseButtonGesture : button = Qt::RightButton; break;
414     case MiddleMouseButtonGesture: button = Qt::MiddleButton; break;
415     default                      :
416     case TouchGesture            : button = Qt::NoButton; break; // NoButton == Touch
417     }
418 
419     sp->recognizer = new QFlickGestureRecognizer(button);
420     sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer);
421 
422     if (target->isWidgetType()) {
423         QWidget *widget = static_cast<QWidget *>(target);
424         widget->grabGesture(sp->recognizerType);
425         if (scrollGestureType == TouchGesture)
426             widget->setAttribute(Qt::WA_AcceptTouchEvents);
427 #if QT_CONFIG(graphicsview)
428     } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
429         if (scrollGestureType == TouchGesture)
430             go->setAcceptTouchEvents(true);
431         go->grabGesture(sp->recognizerType);
432 #endif // QT_CONFIG(graphicsview)
433     }
434     return sp->recognizerType;
435 }
436 
437 /*!
438     Returns the gesture type currently grabbed for the \a target or 0 if no
439     gesture is grabbed.
440 
441     \sa grabGesture(), ungrabGesture()
442 */
grabbedGesture(QObject * target)443 Qt::GestureType QScroller::grabbedGesture(QObject *target)
444 {
445     QScroller *s = scroller(target);
446     if (s && s->d_ptr)
447         return s->d_ptr->recognizerType;
448     else
449         return Qt::GestureType(0);
450 }
451 
452 /*!
453     Ungrabs the gesture for the \a target.
454     Does nothing if no gesture is grabbed.
455 
456     \sa grabGesture(), grabbedGesture()
457 */
ungrabGesture(QObject * target)458 void QScroller::ungrabGesture(QObject *target)
459 {
460     QScroller *s = scroller(target);
461     if (!s)
462         return;
463 
464     QScrollerPrivate *sp = s->d_ptr;
465     if (!sp->recognizer)
466         return; // nothing to do
467 
468     if (target->isWidgetType()) {
469         QWidget *widget = static_cast<QWidget *>(target);
470         widget->ungrabGesture(sp->recognizerType);
471 #if QT_CONFIG(graphicsview)
472     } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
473         go->ungrabGesture(sp->recognizerType);
474 #endif
475     }
476 
477     QGestureRecognizer::unregisterRecognizer(sp->recognizerType);
478     // do not delete the recognizer. The QGestureManager is doing this.
479     sp->recognizer = nullptr;
480 }
481 
482 #endif // QT_NO_GESTURES
483 
484 /*!
485     \internal
486 */
QScroller(QObject * target)487 QScroller::QScroller(QObject *target)
488     : d_ptr(new QScrollerPrivate(this, target))
489 {
490     Q_ASSERT(target); // you can't create a scroller without a target in any normal way
491     setParent(target);
492     Q_D(QScroller);
493     d->init();
494 }
495 
496 /*!
497     \internal
498 */
~QScroller()499 QScroller::~QScroller()
500 {
501     Q_D(QScroller);
502 #ifndef QT_NO_GESTURES
503     QGestureRecognizer::unregisterRecognizer(d->recognizerType);
504     // do not delete the recognizer. The QGestureManager is doing this.
505     d->recognizer = nullptr;
506 #endif
507     qt_allScrollers()->remove(d->target);
508     qt_activeScrollers()->removeOne(this);
509 
510     delete d_ptr;
511 }
512 
513 
514 /*!
515     \fn void QScroller::stateChanged(QScroller::State newState);
516 
517     QScroller emits this signal whenever the state changes. \a newState is the new State.
518 
519     \sa state
520 */
521 
522 /*!
523     \property QScroller::state
524     \brief the state of the scroller
525 
526     \sa QScroller::State
527 */
state() const528 QScroller::State QScroller::state() const
529 {
530     Q_D(const QScroller);
531     return d->state;
532 }
533 
534 /*!
535     Stops the scroller and resets its state back to Inactive.
536 */
stop()537 void QScroller::stop()
538 {
539     Q_D(QScroller);
540     if (d->state != Inactive) {
541         QPointF here = clampToRect(d->contentPosition, d->contentPosRange);
542         qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal);
543         qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical);
544         QPointF snap = here;
545         if (!qIsNaN(snapX))
546             snap.setX(snapX);
547         if (!qIsNaN(snapY))
548             snap.setY(snapY);
549         d->contentPosition = snap;
550         d->overshootPosition = QPointF(0, 0);
551 
552         d->setState(Inactive);
553     }
554 }
555 
556 /*!
557     Returns the pixel per meter metric for the scrolled widget.
558 
559     The value is reported for both the x and y axis separately by using a QPointF.
560 
561     \note Please note that this value should be physically correct. The actual DPI settings
562     that Qt returns for the display may be reported wrongly on purpose by the underlying
563     windowing system, for example on \macos.
564 */
pixelPerMeter() const565 QPointF QScroller::pixelPerMeter() const
566 {
567     Q_D(const QScroller);
568     QPointF ppm = d->pixelPerMeter;
569 
570 #if QT_CONFIG(graphicsview)
571     if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->target)) {
572         QTransform viewtr;
573         //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
574         if (const auto *scene = go->scene()) {
575             const auto views = scene->views();
576             if (!views.isEmpty())
577                 viewtr = views.first()->viewportTransform();
578         }
579         QTransform tr = go->deviceTransform(viewtr);
580         if (tr.isScaling()) {
581             QPointF p0 = tr.map(QPointF(0, 0));
582             QPointF px = tr.map(QPointF(1, 0));
583             QPointF py = tr.map(QPointF(0, 1));
584             ppm.rx() /= QLineF(p0, px).length();
585             ppm.ry() /= QLineF(p0, py).length();
586         }
587     }
588 #endif // QT_CONFIG(graphicsview)
589     return ppm;
590 }
591 
592 /*!
593     Returns the current scrolling velocity in meter per second when the state is Scrolling or Dragging.
594     Returns a zero velocity otherwise.
595 
596     The velocity is reported for both the x and y axis separately by using a QPointF.
597 
598     \sa pixelPerMeter()
599 */
velocity() const600 QPointF QScroller::velocity() const
601 {
602     Q_D(const QScroller);
603     const QScrollerPropertiesPrivate *sp = d->properties.d.data();
604 
605      switch (state()) {
606      case Dragging:
607         return d->releaseVelocity;
608      case Scrolling: {
609         QPointF vel;
610         qint64 now = d->monotonicTimer.elapsed();
611 
612         if (!d->xSegments.isEmpty()) {
613             const QScrollerPrivate::ScrollSegment &s = d->xSegments.head();
614             qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
615             qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
616             vel.setX(v);
617         }
618 
619         if (!d->ySegments.isEmpty()) {
620             const QScrollerPrivate::ScrollSegment &s = d->ySegments.head();
621             qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
622             qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
623             vel.setY(v);
624         }
625         return vel;
626      }
627      default:
628          return QPointF(0, 0);
629      }
630 }
631 
632 /*!
633     Returns the estimated final position for the current scroll movement.
634     Returns the current position if the scroller state is not Scrolling.
635     The result is undefined when the scroller state is Inactive.
636 
637     The target position is in pixel.
638 
639     \sa pixelPerMeter(), scrollTo()
640 */
finalPosition() const641 QPointF QScroller::finalPosition() const
642 {
643     Q_D(const QScroller);
644     return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
645                    d->scrollingSegmentsEndPos(Qt::Vertical));
646 }
647 
648 /*!
649     Starts scrolling the widget so that point \a pos is at the top-left position in
650     the viewport.
651 
652     The behaviour when scrolling outside the valid scroll area is undefined.
653     In this case the scroller might or might not overshoot.
654 
655     The scrolling speed will be calculated so that the given position will
656     be reached after a platform-defined time span.
657 
658     \a pos is given in viewport coordinates.
659 
660     \sa ensureVisible()
661 */
scrollTo(const QPointF & pos)662 void QScroller::scrollTo(const QPointF &pos)
663 {
664     // we could make this adjustable via QScrollerProperties
665     scrollTo(pos, 300);
666 }
667 
668 /*! \overload
669 
670     This version will reach its destination position in \a scrollTime milliseconds.
671 */
scrollTo(const QPointF & pos,int scrollTime)672 void QScroller::scrollTo(const QPointF &pos, int scrollTime)
673 {
674     Q_D(QScroller);
675 
676     if (d->state == Pressed || d->state == Dragging )
677         return;
678 
679     // no need to resend a prepare event if we are already scrolling
680     if (d->state == Inactive && !d->prepareScrolling(QPointF()))
681         return;
682 
683     QPointF newpos = clampToRect(pos, d->contentPosRange);
684     qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal);
685     qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical);
686     if (!qIsNaN(snapX))
687         newpos.setX(snapX);
688     if (!qIsNaN(snapY))
689         newpos.setY(snapY);
690 
691     qScrollerDebug() << "QScroller::scrollTo(req:" << pos << " [pix] / snap:" << newpos << ", " << scrollTime << " [ms])";
692 
693     if (newpos == d->contentPosition + d->overshootPosition)
694         return;
695 
696     QPointF vel = velocity();
697 
698     if (scrollTime < 0)
699         scrollTime = 0;
700     qreal time = qreal(scrollTime) / 1000;
701 
702     d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QScrollerPrivate::ScrollTypeScrollTo);
703     d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QScrollerPrivate::ScrollTypeScrollTo);
704 
705     if (!scrollTime)
706         d->setContentPositionHelperScrolling();
707     d->setState(scrollTime ? Scrolling : Inactive);
708 }
709 
710 /*!
711     Starts scrolling so that the rectangle \a rect is visible inside the
712     viewport with additional margins specified in pixels by \a xmargin and \a ymargin around
713     the rect.
714 
715     In cases where it is not possible to fit the rect plus margins inside the viewport the contents
716     are scrolled so that as much as possible is visible from \a rect.
717 
718     The scrolling speed is calculated so that the given position is reached after a platform-defined
719     time span.
720 
721     This function performs the actual scrolling by calling scrollTo().
722 
723     \sa scrollTo()
724 */
ensureVisible(const QRectF & rect,qreal xmargin,qreal ymargin)725 void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin)
726 {
727     // we could make this adjustable via QScrollerProperties
728     ensureVisible(rect, xmargin, ymargin, 1000);
729 }
730 
731 /*! \overload
732 
733     This version will reach its destination position in \a scrollTime milliseconds.
734 */
ensureVisible(const QRectF & rect,qreal xmargin,qreal ymargin,int scrollTime)735 void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime)
736 {
737     Q_D(QScroller);
738 
739     if (d->state == Pressed || d->state == Dragging )
740         return;
741 
742     if (d->state == Inactive && !d->prepareScrolling(QPointF()))
743         return;
744 
745     // -- calculate the current pos (or the position after the current scroll)
746     QPointF startPos(d->scrollingSegmentsEndPos(Qt::Horizontal),
747                      d->scrollingSegmentsEndPos(Qt::Vertical));
748 
749     QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin,
750                       rect.width() + 2 * xmargin, rect.height() + 2 * ymargin);
751 
752     QSizeF visible = d->viewportSize;
753     QRectF visibleRect(startPos, visible);
754 
755     qScrollerDebug() << "QScroller::ensureVisible(" << rect << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])";
756     qScrollerDebug() << "  --> content position:" << d->contentPosition;
757 
758     if (visibleRect.contains(marginRect))
759         return;
760 
761     QPointF newPos = startPos;
762 
763     if (visibleRect.width() < rect.width()) {
764         // at least try to move the rect into view
765         if (rect.left() > visibleRect.left())
766             newPos.setX(rect.left());
767         else if (rect.right() < visibleRect.right())
768             newPos.setX(rect.right() - visible.width());
769 
770     } else if (visibleRect.width() < marginRect.width()) {
771         newPos.setX(rect.center().x() - visibleRect.width() / 2);
772     } else if (marginRect.left() > visibleRect.left()) {
773         newPos.setX(marginRect.left());
774     } else if (marginRect.right() < visibleRect.right()) {
775         newPos.setX(marginRect.right() - visible.width());
776     }
777 
778     if (visibleRect.height() < rect.height()) {
779         // at least try to move the rect into view
780         if (rect.top() > visibleRect.top())
781             newPos.setX(rect.top());
782         else if (rect.bottom() < visibleRect.bottom())
783             newPos.setX(rect.bottom() - visible.height());
784 
785     } else if (visibleRect.height() < marginRect.height()) {
786         newPos.setY(rect.center().y() - visibleRect.height() / 2);
787     } else if (marginRect.top() > visibleRect.top()) {
788         newPos.setY(marginRect.top());
789     } else if (marginRect.bottom() < visibleRect.bottom()) {
790         newPos.setY(marginRect.bottom() - visible.height());
791     }
792 
793     // clamp to maximum content position
794     newPos = clampToRect(newPos, d->contentPosRange);
795     if (newPos == startPos)
796         return;
797 
798     scrollTo(newPos, scrollTime);
799 }
800 
801 /*! This function resends the QScrollPrepareEvent.
802     Calling resendPrepareEvent triggers a QScrollPrepareEvent from the scroller.
803     This allows the receiver to re-set content position and content size while
804     scrolling.
805     Calling this function while in the Inactive state is useless as the prepare event
806     is sent again before scrolling starts.
807  */
resendPrepareEvent()808 void QScroller::resendPrepareEvent()
809 {
810     Q_D(QScroller);
811     d->prepareScrolling(d->pressPosition);
812 }
813 
814 /*! Set the snap positions for the horizontal axis to a list of \a positions.
815     This overwrites all previously set snap positions and also a previously
816     set snapping interval.
817     Snapping can be deactivated by setting an empty list of positions.
818  */
setSnapPositionsX(const QList<qreal> & positions)819 void QScroller::setSnapPositionsX(const QList<qreal> &positions)
820 {
821     Q_D(QScroller);
822     d->snapPositionsX = positions;
823     d->snapIntervalX = 0.0;
824 
825     d->recalcScrollingSegments();
826 }
827 
828 /*! Set the snap positions for the horizontal axis to regular spaced intervals.
829     The first snap position is at \a first. The next at \a first + \a interval.
830     This can be used to implement a list header.
831     This overwrites all previously set snap positions and also a previously
832     set snapping interval.
833     Snapping can be deactivated by setting an interval of 0.0
834  */
setSnapPositionsX(qreal first,qreal interval)835 void QScroller::setSnapPositionsX(qreal first, qreal interval)
836 {
837     Q_D(QScroller);
838     d->snapFirstX = first;
839     d->snapIntervalX = interval;
840     d->snapPositionsX.clear();
841 
842     d->recalcScrollingSegments();
843 }
844 
845 /*! Set the snap positions for the vertical axis to a list of \a positions.
846     This overwrites all previously set snap positions and also a previously
847     set snapping interval.
848     Snapping can be deactivated by setting an empty list of positions.
849  */
setSnapPositionsY(const QList<qreal> & positions)850 void QScroller::setSnapPositionsY(const QList<qreal> &positions)
851 {
852     Q_D(QScroller);
853     d->snapPositionsY = positions;
854     d->snapIntervalY = 0.0;
855 
856     d->recalcScrollingSegments();
857 }
858 
859 /*! Set the snap positions for the vertical axis to regular spaced intervals.
860     The first snap position is at \a first. The next at \a first + \a interval.
861     This overwrites all previously set snap positions and also a previously
862     set snapping interval.
863     Snapping can be deactivated by setting an interval of 0.0
864  */
setSnapPositionsY(qreal first,qreal interval)865 void QScroller::setSnapPositionsY(qreal first, qreal interval)
866 {
867     Q_D(QScroller);
868     d->snapFirstY = first;
869     d->snapIntervalY = interval;
870     d->snapPositionsY.clear();
871 
872     d->recalcScrollingSegments();
873 }
874 
875 
876 
877 // -------------- private ------------
878 
QScrollerPrivate(QScroller * q,QObject * _target)879 QScrollerPrivate::QScrollerPrivate(QScroller *q, QObject *_target)
880     : target(_target)
881 #ifndef QT_NO_GESTURES
882     , recognizer(nullptr)
883     , recognizerType(Qt::CustomGesture)
884 #endif
885     , state(QScroller::Inactive)
886     , firstScroll(true)
887     , pressTimestamp(0)
888     , lastTimestamp(0)
889     , snapFirstX(-1.0)
890     , snapIntervalX(0.0)
891     , snapFirstY(-1.0)
892     , snapIntervalY(0.0)
893 #if QT_CONFIG(animation)
894     , scrollTimer(new QScrollTimer(this))
895 #endif
896     , q_ptr(q)
897 {
898     connect(target, SIGNAL(destroyed(QObject*)), this, SLOT(targetDestroyed()));
899 }
900 
init()901 void QScrollerPrivate::init()
902 {
903     setDpiFromWidget(nullptr);
904     monotonicTimer.start();
905 }
906 
sendEvent(QObject * o,QEvent * e)907 void QScrollerPrivate::sendEvent(QObject *o, QEvent *e)
908 {
909     qt_sendSpontaneousEvent(o, e);
910 }
911 
stateName(QScroller::State state)912 const char *QScrollerPrivate::stateName(QScroller::State state)
913 {
914     switch (state) {
915     case QScroller::Inactive:  return "inactive";
916     case QScroller::Pressed:   return "pressed";
917     case QScroller::Dragging:  return "dragging";
918     case QScroller::Scrolling: return "scrolling";
919     default:                   return "(invalid)";
920     }
921 }
922 
inputName(QScroller::Input input)923 const char *QScrollerPrivate::inputName(QScroller::Input input)
924 {
925     switch (input) {
926     case QScroller::InputPress:   return "press";
927     case QScroller::InputMove:    return "move";
928     case QScroller::InputRelease: return "release";
929     default:                      return "(invalid)";
930     }
931 }
932 
targetDestroyed()933 void QScrollerPrivate::targetDestroyed()
934 {
935 #if QT_CONFIG(animation)
936     scrollTimer->stop();
937 #endif
938     delete q_ptr;
939 }
940 
timerTick()941 void QScrollerPrivate::timerTick()
942 {
943     struct timerevent {
944         QScroller::State state;
945         typedef void (QScrollerPrivate::*timerhandler_t)();
946         timerhandler_t handler;
947     };
948 
949     timerevent timerevents[] = {
950         { QScroller::Dragging, &QScrollerPrivate::timerEventWhileDragging },
951         { QScroller::Scrolling, &QScrollerPrivate::timerEventWhileScrolling },
952     };
953 
954     for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) {
955         timerevent *te = timerevents + i;
956 
957         if (state == te->state) {
958             (this->*te->handler)();
959             return;
960         }
961     }
962 
963 #if QT_CONFIG(animation)
964     scrollTimer->stop();
965 #endif
966 }
967 
968 /*!
969     This function is used by gesture recognizers to inform the scroller about a new input event.
970     The scroller changes its internal state() according to the input event and its attached
971     scroller properties. The scroller doesn't distinguish between the kind of input device the
972     event came from. Therefore the event needs to be split into the \a input type, a \a position and a
973     milli-second \a timestamp.  The \a position needs to be in the target's coordinate system.
974 
975     The return value is \c true if the event should be consumed by the calling filter or \c false
976     if the event should be forwarded to the control.
977 
978     \note Using grabGesture() should be sufficient for most use cases.
979 */
handleInput(Input input,const QPointF & position,qint64 timestamp)980 bool QScroller::handleInput(Input input, const QPointF &position, qint64 timestamp)
981 {
982     Q_D(QScroller);
983 
984     qScrollerDebug() << "QScroller::handleInput(" << input << ", " << d->stateName(d->state) << ", " << position << ", " << timestamp << ')';
985     struct statechange {
986         State state;
987         Input input;
988         typedef bool (QScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp);
989         inputhandler_t handler;
990     };
991 
992     statechange statechanges[] = {
993         { QScroller::Inactive,  InputPress,   &QScrollerPrivate::pressWhileInactive },
994         { QScroller::Pressed,   InputMove,    &QScrollerPrivate::moveWhilePressed },
995         { QScroller::Pressed,   InputRelease, &QScrollerPrivate::releaseWhilePressed },
996         { QScroller::Dragging,  InputMove,    &QScrollerPrivate::moveWhileDragging },
997         { QScroller::Dragging,  InputRelease, &QScrollerPrivate::releaseWhileDragging },
998         { QScroller::Scrolling, InputPress,   &QScrollerPrivate::pressWhileScrolling }
999     };
1000 
1001     for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) {
1002         statechange *sc = statechanges + i;
1003 
1004          if (d->state == sc->state && input == sc->input)
1005              return (d->*sc->handler)(position - d->overshootPosition, timestamp);
1006     }
1007     return false;
1008 }
1009 
1010 /*! \internal
1011     Returns the resolution of the used screen.
1012 */
dpi() const1013 QPointF QScrollerPrivate::dpi() const
1014 {
1015     return pixelPerMeter * qreal(0.0254);
1016 }
1017 
1018 /*! \internal
1019     Sets the resolution used for scrolling.
1020     This resolution is only used by the kinetic scroller. If you change this
1021     then the scroller will behave quite different as a lot of the values are
1022     given in physical distances (millimeter).
1023 */
setDpi(const QPointF & dpi)1024 void QScrollerPrivate::setDpi(const QPointF &dpi)
1025 {
1026     pixelPerMeter = dpi / qreal(0.0254);
1027 }
1028 
1029 /*! \internal
1030     Sets the dpi used for scrolling to the value of the widget.
1031 */
setDpiFromWidget(QWidget * widget)1032 void QScrollerPrivate::setDpiFromWidget(QWidget *widget)
1033 {
1034     const QScreen *screen = widget ? widget->screen() : QGuiApplication::primaryScreen();
1035     Q_ASSERT(screen);
1036     setDpi(QPointF(screen->physicalDotsPerInchX(), screen->physicalDotsPerInchY()));
1037 }
1038 
1039 /*! \internal
1040     Updates the velocity during dragging.
1041     Sets releaseVelocity.
1042 */
updateVelocity(const QPointF & deltaPixelRaw,qint64 deltaTime)1043 void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime)
1044 {
1045     if (deltaTime <= 0)
1046         return;
1047 
1048     Q_Q(QScroller);
1049     QPointF ppm = q->pixelPerMeter();
1050     const QScrollerPropertiesPrivate *sp = properties.d.data();
1051     QPointF deltaPixel = deltaPixelRaw;
1052 
1053     qScrollerDebug() << "QScroller::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])";
1054 
1055     // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms)
1056     if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5))
1057         deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength();
1058 
1059     QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm;
1060     // around 95% of all updates are in the [1..50] ms range, so make sure
1061     // to scale the smoothing factor over that range: this way a 50ms update
1062     // will have full impact, while 5ms update will only have a 10% impact.
1063     qreal smoothing = sp->dragVelocitySmoothingFactor * qMin(qreal(deltaTime), qreal(50)) / qreal(50);
1064 
1065     // only smooth if we already have a release velocity and only if the
1066     // user hasn't stopped to move his finger for more than 100ms
1067     if ((releaseVelocity != QPointF(0, 0)) && (deltaTime < 100)) {
1068         qScrollerDebug() << "SMOOTHED from " << newv << " to " << newv * smoothing + releaseVelocity * (qreal(1) - smoothing);
1069         // smooth x or y only if the new velocity is either 0 or at least in
1070         // the same direction of the release velocity
1071         if (!newv.x() || (qSign(releaseVelocity.x()) == qSign(newv.x())))
1072             newv.setX(newv.x() * smoothing + releaseVelocity.x() * (qreal(1) - smoothing));
1073         if (!newv.y() || (qSign(releaseVelocity.y()) == qSign(newv.y())))
1074             newv.setY(newv.y() * smoothing + releaseVelocity.y() * (qreal(1) - smoothing));
1075     } else
1076         qScrollerDebug() << "NO SMOOTHING to " << newv;
1077 
1078     releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity));
1079     releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity));
1080 
1081     qScrollerDebug() << "  --> new velocity:" << releaseVelocity;
1082 }
1083 
pushSegment(ScrollType type,qreal deltaTime,qreal stopProgress,qreal startPos,qreal deltaPos,qreal stopPos,QEasingCurve::Type curve,Qt::Orientation orientation)1084 void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation)
1085 {
1086     if (startPos == stopPos || deltaPos == 0)
1087         return;
1088 
1089     ScrollSegment s;
1090     if (orientation == Qt::Horizontal && !xSegments.isEmpty()) {
1091         const auto &lastX = xSegments.constLast();
1092         s.startTime = lastX.startTime + lastX.deltaTime * lastX.stopProgress;
1093     } else if (orientation == Qt::Vertical && !ySegments.isEmpty()) {
1094         const auto &lastY = ySegments.constLast();
1095         s.startTime = lastY.startTime + lastY.deltaTime * lastY.stopProgress;
1096     } else {
1097         s.startTime = monotonicTimer.elapsed();
1098     }
1099 
1100     s.startPos = startPos;
1101     s.deltaPos = deltaPos;
1102     s.stopPos = stopPos;
1103     s.deltaTime = deltaTime * 1000;
1104     s.stopProgress = stopProgress;
1105     s.curve.setType(curve);
1106     s.type = type;
1107 
1108     if (orientation == Qt::Horizontal)
1109         xSegments.enqueue(s);
1110     else
1111         ySegments.enqueue(s);
1112 
1113     qScrollerDebug() << "+++ Added a new ScrollSegment: " << s;
1114 }
1115 
1116 
1117 /*! \internal
1118     Clears the old segments and recalculates them if the current segments are not longer valid
1119 */
recalcScrollingSegments(bool forceRecalc)1120 void QScrollerPrivate::recalcScrollingSegments(bool forceRecalc)
1121 {
1122     Q_Q(QScroller);
1123     QPointF ppm = q->pixelPerMeter();
1124 
1125     releaseVelocity = q->velocity();
1126 
1127     if (forceRecalc ||
1128         !scrollingSegmentsValid(Qt::Horizontal) ||
1129         !scrollingSegmentsValid(Qt::Vertical))
1130         createScrollingSegments(releaseVelocity, contentPosition + overshootPosition, ppm);
1131 }
1132 
1133 /*! \internal
1134     Returns the end position after the current scroll has finished.
1135 */
scrollingSegmentsEndPos(Qt::Orientation orientation) const1136 qreal QScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const
1137 {
1138     if (orientation == Qt::Horizontal) {
1139         if (xSegments.isEmpty())
1140             return contentPosition.x() + overshootPosition.x();
1141         else
1142             return xSegments.last().stopPos;
1143     } else {
1144         if (ySegments.isEmpty())
1145             return contentPosition.y() + overshootPosition.y();
1146         else
1147             return ySegments.last().stopPos;
1148     }
1149 }
1150 
1151 /*! \internal
1152     Checks if the scroller segment end in a valid position.
1153 */
scrollingSegmentsValid(Qt::Orientation orientation) const1154 bool QScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation) const
1155 {
1156     const QQueue<ScrollSegment> *segments;
1157     qreal minPos;
1158     qreal maxPos;
1159 
1160     if (orientation == Qt::Horizontal) {
1161         segments = &xSegments;
1162         minPos = contentPosRange.left();
1163         maxPos = contentPosRange.right();
1164     } else {
1165         segments = &ySegments;
1166         minPos = contentPosRange.top();
1167         maxPos = contentPosRange.bottom();
1168     }
1169 
1170     if (segments->isEmpty())
1171         return true;
1172 
1173     const ScrollSegment &last = segments->last();
1174     qreal stopPos = last.stopPos;
1175 
1176     if (last.type == ScrollTypeScrollTo)
1177         return true; // scrollTo is always valid
1178 
1179     if (last.type == ScrollTypeOvershoot &&
1180         (stopPos != minPos && stopPos != maxPos))
1181         return false;
1182 
1183     if (stopPos < minPos || stopPos > maxPos)
1184         return false;
1185 
1186     if (stopPos == minPos || stopPos == maxPos) // the begin and the end of the list are always ok
1187         return true;
1188 
1189     qreal nextSnap = nextSnapPos(stopPos, 0, orientation);
1190     if (!qIsNaN(nextSnap) && stopPos != nextSnap)
1191         return false;
1192 
1193     return true;
1194 }
1195 
1196 /*! \internal
1197    Creates the sections needed to scroll to the specific \a endPos to the segments queue.
1198 */
createScrollToSegments(qreal v,qreal deltaTime,qreal endPos,Qt::Orientation orientation,ScrollType type)1199 void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type)
1200 {
1201     Q_UNUSED(v);
1202 
1203     if (orientation == Qt::Horizontal)
1204         xSegments.clear();
1205     else
1206         ySegments.clear();
1207 
1208     qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation);
1209 
1210     const QScrollerPropertiesPrivate *sp = properties.d.data();
1211 
1212     qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x()
1213                                                      : contentPosition.y() + overshootPosition.y();
1214     qreal deltaPos = (endPos - startPos) / 2;
1215 
1216     pushSegment(type, deltaTime * qreal(0.3), qreal(1.0), startPos, deltaPos, startPos + deltaPos, QEasingCurve::InQuad, orientation);
1217     pushSegment(type, deltaTime * qreal(0.7), qreal(1.0), startPos + deltaPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
1218 }
1219 
1220 /*! \internal
1221 */
createScrollingSegments(qreal v,qreal startPos,qreal deltaTime,qreal deltaPos,Qt::Orientation orientation)1222 void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos,
1223                                                qreal deltaTime, qreal deltaPos,
1224                                                Qt::Orientation orientation)
1225 {
1226     const QScrollerPropertiesPrivate *sp = properties.d.data();
1227 
1228     QScrollerProperties::OvershootPolicy policy;
1229     qreal minPos;
1230     qreal maxPos;
1231     qreal viewSize;
1232 
1233     if (orientation == Qt::Horizontal) {
1234         xSegments.clear();
1235         policy = sp->hOvershootPolicy;
1236         minPos = contentPosRange.left();
1237         maxPos = contentPosRange.right();
1238         viewSize = viewportSize.width();
1239     } else {
1240         ySegments.clear();
1241         policy = sp->vOvershootPolicy;
1242         minPos = contentPosRange.top();
1243         maxPos = contentPosRange.bottom();
1244         viewSize = viewportSize.height();
1245     }
1246 
1247     bool alwaysOvershoot = (policy == QScrollerProperties::OvershootAlwaysOn);
1248     bool noOvershoot = (policy == QScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor;
1249     bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos);
1250 
1251     qScrollerDebug() << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos << "o:" << int(orientation);
1252 
1253     qScrollerDebug() << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor << ", curveType = " << sp->scrollingCurve.type();
1254 
1255     qreal endPos = startPos + deltaPos;
1256 
1257     qScrollerDebug() << "  Real Delta:" << deltaPos;
1258 
1259     // -- check if are in overshoot and end in overshoot
1260     if ((startPos < minPos && endPos < minPos) ||
1261         (startPos > maxPos && endPos > maxPos)) {
1262         qreal stopPos = endPos < minPos ? minPos : maxPos;
1263         qreal oDeltaTime = sp->overshootScrollTime;
1264 
1265         pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), startPos, stopPos - startPos, stopPos, sp->scrollingCurve.type(), orientation);
1266         return;
1267     }
1268 
1269     // -- determine snap points
1270     qreal nextSnap = nextSnapPos(endPos, 0, orientation);
1271     qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation);
1272     qreal higherSnapPos = nextSnapPos(startPos, 1, orientation);
1273 
1274     qScrollerDebug() << "  Real Delta:" << lowerSnapPos << '-' << nextSnap << '-' <<higherSnapPos;
1275 
1276     // - check if we can reach another snap point
1277     if (nextSnap > higherSnapPos || qIsNaN(higherSnapPos))
1278         higherSnapPos = nextSnap;
1279     if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos))
1280         lowerSnapPos = nextSnap;
1281 
1282     if (qAbs(v) < sp->minimumVelocity) {
1283 
1284         qScrollerDebug() << "### below minimum Vel" << orientation;
1285 
1286         // - no snap points or already at one
1287         if (qIsNaN(nextSnap) || nextSnap == startPos)
1288             return; // nothing to do, no scrolling needed.
1289 
1290         // - decide which point to use
1291 
1292         qreal snapDistance = higherSnapPos - lowerSnapPos;
1293 
1294         qreal pressDistance = (orientation == Qt::Horizontal) ?
1295             lastPosition.x() - pressPosition.x() :
1296             lastPosition.y() - pressPosition.y();
1297 
1298         // if not dragged far enough, pick the next snap point.
1299         if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance)
1300             endPos = nextSnap;
1301         else if (pressDistance < 0.0)
1302             endPos = lowerSnapPos;
1303         else
1304             endPos = higherSnapPos;
1305 
1306         deltaPos = endPos - startPos;
1307         qreal midPos = startPos + deltaPos * qreal(0.3);
1308         pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.3), qreal(1.0), startPos, midPos - startPos, midPos, QEasingCurve::InQuad, orientation);
1309         pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.7), qreal(1.0), midPos, endPos - midPos, endPos, sp->scrollingCurve.type(), orientation);
1310         return;
1311     }
1312 
1313     // - go to the next snappoint if there is one
1314     if (v > 0 && !qIsNaN(higherSnapPos)) {
1315         // change the time in relation to the changed end position
1316         if (endPos - startPos)
1317             deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos));
1318         if (deltaTime > sp->snapTime)
1319             deltaTime = sp->snapTime;
1320         endPos = higherSnapPos;
1321 
1322     } else if (v < 0 && !qIsNaN(lowerSnapPos)) {
1323         // change the time in relation to the changed end position
1324         if (endPos - startPos)
1325             deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos));
1326         if (deltaTime > sp->snapTime)
1327             deltaTime = sp->snapTime;
1328         endPos = lowerSnapPos;
1329 
1330     // -- check if we are overshooting
1331     } else if (endPos < minPos || endPos > maxPos) {
1332         qreal stopPos = endPos < minPos ? minPos : maxPos;
1333 
1334         qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos);
1335 
1336         qreal stopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos));
1337 
1338         if (!canOvershoot) {
1339             qScrollerDebug() << "Overshoot stopp:" << stopProgress;
1340 
1341             pushSegment(ScrollTypeFlick, deltaTime, stopProgress, startPos, endPos, stopPos, sp->scrollingCurve.type(), orientation);
1342         } else {
1343             qreal oDeltaTime = sp->overshootScrollTime;
1344             qreal oStopProgress = qMin(stopProgress + oDeltaTime * qreal(0.3) / deltaTime, qreal(1));
1345             qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(oStopProgress) - stopPos;
1346             qreal oMaxDistance = qSign(oDistance) * (viewSize * sp->overshootScrollDistanceFactor);
1347 
1348             qScrollerDebug() << "1 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
1349 
1350             if (qAbs(oDistance) > qAbs(oMaxDistance)) {
1351                 oStopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos + oMaxDistance - startPos) / deltaPos));
1352                 oDistance = oMaxDistance;
1353                 qScrollerDebug() << "2 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
1354             }
1355 
1356             pushSegment(ScrollTypeFlick, deltaTime, oStopProgress, startPos, deltaPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation);
1357             pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), stopPos + oDistance, -oDistance, stopPos, sp->scrollingCurve.type(), orientation);
1358         }
1359         return;
1360     }
1361 
1362     pushSegment(ScrollTypeFlick, deltaTime, qreal(1.0), startPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
1363 }
1364 
1365 
createScrollingSegments(const QPointF & v,const QPointF & startPos,const QPointF & ppm)1366 void QScrollerPrivate::createScrollingSegments(const QPointF &v,
1367                                                const QPointF &startPos,
1368                                                const QPointF &ppm)
1369 {
1370     const QScrollerPropertiesPrivate *sp = properties.d.data();
1371 
1372     // This is only correct for QEasingCurve::OutQuad (linear velocity,
1373     // constant deceleration), but the results look and feel ok for OutExpo
1374     // and OutSine as well
1375 
1376     // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime)
1377     // v(0) = vrelease
1378     // v(deltaTime) = 0
1379     // deltaTime = (2 * vrelease) / (a * differntial(0))
1380 
1381     // pos(t) = integrate(v(t)dt)
1382     // pos(t) = vrelease * t - 0.5 * a * t * t
1383     // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime
1384     // deltaPos = pos(deltaTime)
1385 
1386     QVector2D vel(v);
1387     qreal deltaTime = (qreal(2) * vel.length()) / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0));
1388     QPointF deltaPos = (vel.normalized() * QVector2D(ppm)).toPointF() * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor;
1389 
1390     createScrollingSegments(v.x(), startPos.x(), deltaTime, deltaPos.x(),
1391                             Qt::Horizontal);
1392     createScrollingSegments(v.y(), startPos.y(), deltaTime, deltaPos.y(),
1393                             Qt::Vertical);
1394 }
1395 
1396 /*! \internal
1397     Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget.
1398     Returns \c true if the scrolling was accepted and a target was returned.
1399 */
prepareScrolling(const QPointF & position)1400 bool QScrollerPrivate::prepareScrolling(const QPointF &position)
1401 {
1402     QScrollPrepareEvent spe(position);
1403     spe.ignore();
1404     sendEvent(target, &spe);
1405 
1406     qScrollerDebug() << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted() << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos();
1407     if (spe.isAccepted()) {
1408         QPointF oldContentPos = contentPosition + overshootPosition;
1409         QPointF contentDelta = spe.contentPos() - oldContentPos;
1410 
1411         viewportSize = spe.viewportSize();
1412         contentPosRange = spe.contentPosRange();
1413         if (contentPosRange.width() < 0)
1414             contentPosRange.setWidth(0);
1415         if (contentPosRange.height() < 0)
1416             contentPosRange.setHeight(0);
1417         contentPosition = clampToRect(spe.contentPos(), contentPosRange);
1418         overshootPosition = spe.contentPos() - contentPosition;
1419 
1420         // - check if the content position was moved
1421         if (contentDelta != QPointF(0, 0)) {
1422             // need to correct all segments
1423             for (int i = 0; i < xSegments.count(); i++)
1424                 xSegments[i].startPos -= contentDelta.x();
1425 
1426             for (int i = 0; i < ySegments.count(); i++)
1427                 ySegments[i].startPos -= contentDelta.y();
1428         }
1429 
1430         if (QWidget *w = qobject_cast<QWidget *>(target))
1431             setDpiFromWidget(w);
1432 #if QT_CONFIG(graphicsview)
1433         if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(target)) {
1434             //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
1435             if (const auto *scene = go->scene()) {
1436                 const auto views = scene->views();
1437                 if (!views.isEmpty())
1438                     setDpiFromWidget(views.first());
1439             }
1440         }
1441 #endif
1442 
1443         if (state == QScroller::Scrolling) {
1444             recalcScrollingSegments();
1445         }
1446         return true;
1447     }
1448 
1449     return false;
1450 }
1451 
handleDrag(const QPointF & position,qint64 timestamp)1452 void QScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp)
1453 {
1454     const QScrollerPropertiesPrivate *sp = properties.d.data();
1455 
1456     QPointF deltaPixel = position - lastPosition;
1457     qint64 deltaTime = timestamp - lastTimestamp;
1458 
1459     if (sp->axisLockThreshold) {
1460         int dx = qAbs(deltaPixel.x());
1461         int dy = qAbs(deltaPixel.y());
1462         if (dx || dy) {
1463             bool vertical = (dy > dx);
1464             qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
1465             //qScrollerDebug() << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy;
1466             if (alpha <= sp->axisLockThreshold) {
1467                 if (vertical)
1468                     deltaPixel.setX(0);
1469                 else
1470                     deltaPixel.setY(0);
1471             }
1472         }
1473     }
1474 
1475     // calculate velocity (if the user would release the mouse NOW)
1476     updateVelocity(deltaPixel, deltaTime);
1477 
1478     // restrict velocity, if content is not scrollable
1479     QRectF max = contentPosRange;
1480     bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1481     bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1482 
1483     if (!canScrollX) {
1484         deltaPixel.setX(0);
1485         releaseVelocity.setX(0);
1486     }
1487     if (!canScrollY) {
1488         deltaPixel.setY(0);
1489         releaseVelocity.setY(0);
1490     }
1491 
1492 //    if (firstDrag) {
1493 //        // Do not delay the first drag
1494 //        setContentPositionHelper(q->contentPosition() - overshootDistance - deltaPixel);
1495 //        dragDistance = QPointF(0, 0);
1496 //    } else {
1497     dragDistance += deltaPixel;
1498 //    }
1499 //qScrollerDebug() << "######################" << deltaPixel << position.y() << lastPosition.y();
1500 
1501     lastPosition = position;
1502     lastTimestamp = timestamp;
1503 }
1504 
pressWhileInactive(const QPointF & position,qint64 timestamp)1505 bool QScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp)
1506 {
1507     if (prepareScrolling(position)) {
1508         const QScrollerPropertiesPrivate *sp = properties.d.data();
1509 
1510         if (!contentPosRange.isNull() ||
1511             (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) ||
1512             (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) {
1513 
1514             lastPosition = pressPosition = position;
1515             lastTimestamp = pressTimestamp = timestamp;
1516             setState(QScroller::Pressed);
1517         }
1518     }
1519     return false;
1520 }
1521 
releaseWhilePressed(const QPointF &,qint64)1522 bool QScrollerPrivate::releaseWhilePressed(const QPointF &, qint64)
1523 {
1524     if (overshootPosition != QPointF(0.0, 0.0)) {
1525         setState(QScroller::Scrolling);
1526         return true;
1527     } else {
1528         setState(QScroller::Inactive);
1529         return false;
1530     }
1531 }
1532 
moveWhilePressed(const QPointF & position,qint64 timestamp)1533 bool QScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp)
1534 {
1535     Q_Q(QScroller);
1536     const QScrollerPropertiesPrivate *sp = properties.d.data();
1537     QPointF ppm = q->pixelPerMeter();
1538 
1539     QPointF deltaPixel = position - pressPosition;
1540 
1541     bool moveAborted = false;
1542     bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance);
1543 
1544     // check the direction of the mouse drag and abort if it's too much in the wrong direction.
1545     if (moveStarted) {
1546         QRectF max = contentPosRange;
1547         bool canScrollX = (max.width() > 0);
1548         bool canScrollY = (max.height() > 0);
1549 
1550         if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1551             canScrollX = true;
1552         if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1553             canScrollY = true;
1554 
1555         if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) {
1556             if (!canScrollY)
1557                 moveAborted = true;
1558         } else {
1559             if (!canScrollX)
1560                 moveAborted = true;
1561         }
1562     }
1563 
1564     if (moveAborted) {
1565         setState(QScroller::Inactive);
1566         moveStarted = false;
1567 
1568     } else if (moveStarted) {
1569         setState(QScroller::Dragging);
1570 
1571         // subtract the dragStartDistance
1572         deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength());
1573 
1574         if (deltaPixel != QPointF(0, 0)) {
1575             // handleDrag updates lastPosition, lastTimestamp and velocity
1576             handleDrag(pressPosition + deltaPixel, timestamp);
1577         }
1578     }
1579     return moveStarted;
1580 }
1581 
moveWhileDragging(const QPointF & position,qint64 timestamp)1582 bool QScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp)
1583 {
1584     // handleDrag updates lastPosition, lastTimestamp and velocity
1585     handleDrag(position, timestamp);
1586     return true;
1587 }
1588 
timerEventWhileDragging()1589 void QScrollerPrivate::timerEventWhileDragging()
1590 {
1591     if (dragDistance != QPointF(0, 0)) {
1592         qScrollerDebug() << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance;
1593 
1594         setContentPositionHelperDragging(-dragDistance);
1595         dragDistance = QPointF(0, 0);
1596     }
1597 }
1598 
releaseWhileDragging(const QPointF & position,qint64 timestamp)1599 bool QScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp)
1600 {
1601     Q_Q(QScroller);
1602     const QScrollerPropertiesPrivate *sp = properties.d.data();
1603 
1604     // handleDrag updates lastPosition, lastTimestamp and velocity
1605     handleDrag(position, timestamp);
1606 
1607     // check if we moved at all - this can happen if you stop a running
1608     // scroller with a press and release shortly afterwards
1609     QPointF deltaPixel = position - pressPosition;
1610     if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) {
1611 
1612         // handle accelerating flicks
1613         if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime &&
1614             ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) {
1615 
1616             // - determine if the direction was changed
1617             int signX = 0, signY = 0;
1618             if (releaseVelocity.x())
1619                 signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1;
1620             if (releaseVelocity.y())
1621                 signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1;
1622 
1623             if (signX > 0)
1624                 releaseVelocity.setX(qBound(-sp->maximumVelocity,
1625                                             oldVelocity.x() * sp->acceleratingFlickSpeedupFactor,
1626                                             sp->maximumVelocity));
1627             if (signY > 0)
1628                 releaseVelocity.setY(qBound(-sp->maximumVelocity,
1629                                             oldVelocity.y() * sp->acceleratingFlickSpeedupFactor,
1630                                             sp->maximumVelocity));
1631         }
1632     }
1633 
1634     QPointF ppm = q->pixelPerMeter();
1635     createScrollingSegments(releaseVelocity, contentPosition + overshootPosition, ppm);
1636 
1637     qScrollerDebug() << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition;
1638 
1639     if (xSegments.isEmpty() && ySegments.isEmpty())
1640         setState(QScroller::Inactive);
1641     else
1642         setState(QScroller::Scrolling);
1643 
1644     return true;
1645 }
1646 
timerEventWhileScrolling()1647 void QScrollerPrivate::timerEventWhileScrolling()
1648 {
1649     qScrollerDebug("QScroller::timerEventWhileScrolling()");
1650 
1651     setContentPositionHelperScrolling();
1652     if (xSegments.isEmpty() && ySegments.isEmpty())
1653         setState(QScroller::Inactive);
1654 }
1655 
pressWhileScrolling(const QPointF & position,qint64 timestamp)1656 bool QScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp)
1657 {
1658     Q_Q(QScroller);
1659 
1660     if ((q->velocity() <= properties.d->maximumClickThroughVelocity) &&
1661         (overshootPosition == QPointF(0.0, 0.0))) {
1662         setState(QScroller::Inactive);
1663         return false;
1664     } else {
1665         lastPosition = pressPosition = position;
1666         lastTimestamp = pressTimestamp = timestamp;
1667         setState(QScroller::Pressed);
1668         setState(QScroller::Dragging);
1669         return true;
1670     }
1671 }
1672 
1673 /*! \internal
1674     This function handles all state changes of the scroller.
1675 */
setState(QScroller::State newstate)1676 void QScrollerPrivate::setState(QScroller::State newstate)
1677 {
1678     Q_Q(QScroller);
1679     bool sendLastScroll = false;
1680 
1681     if (state == newstate)
1682         return;
1683 
1684     qScrollerDebug() << q << "QScroller::setState(" << stateName(newstate) << ')';
1685 
1686     switch (newstate) {
1687     case QScroller::Inactive:
1688 #if QT_CONFIG(animation)
1689         scrollTimer->stop();
1690 #endif
1691 
1692         // send the last scroll event (but only after the current state change was finished)
1693         if (!firstScroll)
1694             sendLastScroll = true;
1695 
1696         releaseVelocity = QPointF(0, 0);
1697         break;
1698 
1699     case QScroller::Pressed:
1700 #if QT_CONFIG(animation)
1701         scrollTimer->stop();
1702 #endif
1703 
1704         oldVelocity = releaseVelocity;
1705         releaseVelocity = QPointF(0, 0);
1706         break;
1707 
1708     case QScroller::Dragging:
1709         dragDistance = QPointF(0, 0);
1710 #if QT_CONFIG(animation)
1711         if (state == QScroller::Pressed)
1712             scrollTimer->start();
1713 #endif
1714         break;
1715 
1716     case QScroller::Scrolling:
1717 #if QT_CONFIG(animation)
1718         scrollTimer->start();
1719 #endif
1720         break;
1721     }
1722 
1723     qSwap(state, newstate);
1724 
1725     if (sendLastScroll) {
1726         QScrollEvent se(contentPosition, overshootPosition, QScrollEvent::ScrollFinished);
1727         sendEvent(target, &se);
1728         firstScroll = true;
1729     }
1730     if (state == QScroller::Dragging || state == QScroller::Scrolling) {
1731         if (!qt_activeScrollers()->contains(q))
1732             qt_activeScrollers()->push_back(q);
1733     } else {
1734         qt_activeScrollers()->removeOne(q);
1735     }
1736     emit q->stateChanged(state);
1737 }
1738 
1739 
1740 /*! \internal
1741     Helps when setting the content position.
1742     It will try to move the content by the requested delta but stop in case
1743     when we are coming back from an overshoot or a scrollTo.
1744     It will also indicate a new overshooting condition by the overshootX and oversthootY flags.
1745 
1746     In this cases it will reset the velocity variables and other flags.
1747 
1748     Also keeps track of the current over-shooting value in overshootPosition.
1749 
1750     \a deltaPos is the amount of pixels the current content position should be moved
1751 */
setContentPositionHelperDragging(const QPointF & deltaPos)1752 void QScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos)
1753 {
1754     const QScrollerPropertiesPrivate *sp = properties.d.data();
1755 
1756     if (sp->overshootDragResistanceFactor)
1757         overshootPosition /= sp->overshootDragResistanceFactor;
1758 
1759     QPointF oldPos = contentPosition + overshootPosition;
1760     QPointF newPos = oldPos + deltaPos;
1761 
1762     qScrollerDebug() << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])";
1763     qScrollerDebug() << "  --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos;
1764 
1765     QPointF oldClampedPos = clampToRect(oldPos, contentPosRange);
1766     QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1767 
1768     // --- handle overshooting and stop if the coordinate is going back inside the normal area
1769     bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1770     bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1771     bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1772                         ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1773                         !sp->overshootDragDistanceFactor;
1774     bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1775                         ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1776                         !sp->overshootDragDistanceFactor;
1777     bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width());
1778     bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height());
1779 
1780     qreal oldOvershootX = (canOvershootX) ? oldPos.x() - oldClampedPos.x() : 0;
1781     qreal oldOvershootY = (canOvershootY) ? oldPos.y() - oldClampedPos.y() : 0;
1782 
1783     qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0;
1784     qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0;
1785 
1786     qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor;
1787     qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor;
1788 
1789     qScrollerDebug() << "  --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy;
1790     qScrollerDebug() << "  --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX;
1791 
1792     if (sp->overshootDragResistanceFactor) {
1793         oldOvershootX *= sp->overshootDragResistanceFactor;
1794         oldOvershootY *= sp->overshootDragResistanceFactor;
1795         newOvershootX *= sp->overshootDragResistanceFactor;
1796         newOvershootY *= sp->overshootDragResistanceFactor;
1797     }
1798 
1799     // -- stop at the maximum overshoot distance
1800 
1801     newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX);
1802     newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY);
1803 
1804     overshootPosition.setX(newOvershootX);
1805     overshootPosition.setY(newOvershootY);
1806     contentPosition = newClampedPos;
1807 
1808     QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
1809     sendEvent(target, &se);
1810     firstScroll = false;
1811 
1812     qScrollerDebug() << "  --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition <<
1813                         "- overshoot x/y?:" << overshootPosition;
1814 }
1815 
1816 
nextSegmentPosition(QQueue<ScrollSegment> & segments,qint64 now,qreal oldPos)1817 qreal QScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos)
1818 {
1819     qreal pos = oldPos;
1820 
1821     // check the X segments for new positions
1822     while (!segments.isEmpty()) {
1823         const ScrollSegment s = segments.head();
1824 
1825         if ((s.startTime + s.deltaTime * s.stopProgress) <= now) {
1826             segments.dequeue();
1827             pos = s.stopPos;
1828         } else if (s.startTime <= now) {
1829             qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
1830             pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress);
1831             if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) {
1832                 segments.dequeue();
1833                 pos = s.stopPos;
1834             } else {
1835                 break;
1836             }
1837         } else {
1838             break;
1839         }
1840     }
1841     return pos;
1842 }
1843 
setContentPositionHelperScrolling()1844 void QScrollerPrivate::setContentPositionHelperScrolling()
1845 {
1846     qint64 now = monotonicTimer.elapsed();
1847     QPointF newPos = contentPosition + overshootPosition;
1848 
1849     newPos.setX(nextSegmentPosition(xSegments, now, newPos.x()));
1850     newPos.setY(nextSegmentPosition(ySegments, now, newPos.y()));
1851 
1852     // -- set the position and handle overshoot
1853     qScrollerDebug() << "QScroller::setContentPositionHelperScrolling()\n"
1854                         "  --> overshoot:" << overshootPosition << "- new pos:" << newPos;
1855 
1856     QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1857 
1858     overshootPosition = newPos - newClampedPos;
1859     contentPosition = newClampedPos;
1860 
1861     QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
1862     sendEvent(target, &se);
1863     firstScroll = false;
1864 
1865     qScrollerDebug() << "  --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition;
1866 }
1867 
1868 /*! \internal
1869     Returns the next snap point in direction.
1870     If \a direction >0 it will return the next snap point that is larger than the current position.
1871     If \a direction <0 it will return the next snap point that is smaller than the current position.
1872     If \a direction ==0 it will return the nearest snap point (or the current position if we are already
1873     on a snap point.
1874     Returns the nearest snap position or NaN if no such point could be found.
1875  */
nextSnapPos(qreal p,int dir,Qt::Orientation orientation) const1876 qreal QScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation) const
1877 {
1878     qreal bestSnapPos = Q_QNAN;
1879     qreal bestSnapPosDist = Q_INFINITY;
1880 
1881     qreal minPos;
1882     qreal maxPos;
1883 
1884     if (orientation == Qt::Horizontal) {
1885         minPos = contentPosRange.left();
1886         maxPos = contentPosRange.right();
1887     } else {
1888         minPos = contentPosRange.top();
1889         maxPos = contentPosRange.bottom();
1890     }
1891 
1892     if (orientation == Qt::Horizontal) {
1893         // the snap points in the list
1894         for (qreal snapPos : snapPositionsX) {
1895             qreal snapPosDist = snapPos - p;
1896             if ((dir > 0 && snapPosDist < 0) ||
1897                 (dir < 0 && snapPosDist > 0))
1898                 continue; // wrong direction
1899             if (snapPos < minPos || snapPos > maxPos )
1900                 continue; // invalid
1901 
1902             if (qIsNaN(bestSnapPos) ||
1903                 qAbs(snapPosDist) < bestSnapPosDist ) {
1904                 bestSnapPos = snapPos;
1905                 bestSnapPosDist = qAbs(snapPosDist);
1906             }
1907         }
1908 
1909         // the snap point interval
1910         if (snapIntervalX > 0.0) {
1911             qreal first = minPos + snapFirstX;
1912             qreal snapPos;
1913             if (dir > 0)
1914                 snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first;
1915             else if (dir < 0)
1916                 snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first;
1917             else if (p <= first)
1918                 snapPos = first;
1919             else
1920             {
1921                 qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first;
1922                 if (p >= last)
1923                     snapPos = last;
1924                 else
1925                     snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first;
1926             }
1927 
1928             if (snapPos >= first && snapPos <= maxPos ) {
1929                 qreal snapPosDist = snapPos - p;
1930 
1931                 if (qIsNaN(bestSnapPos) ||
1932                     qAbs(snapPosDist) < bestSnapPosDist ) {
1933                     bestSnapPos = snapPos;
1934                     bestSnapPosDist = qAbs(snapPosDist);
1935                 }
1936             }
1937         }
1938 
1939     } else { // (orientation == Qt::Vertical)
1940         // the snap points in the list
1941         for (qreal snapPos : snapPositionsY) {
1942             qreal snapPosDist = snapPos - p;
1943             if ((dir > 0 && snapPosDist < 0) ||
1944                 (dir < 0 && snapPosDist > 0))
1945                 continue; // wrong direction
1946             if (snapPos < minPos || snapPos > maxPos )
1947                 continue; // invalid
1948 
1949             if (qIsNaN(bestSnapPos) ||
1950                 qAbs(snapPosDist) < bestSnapPosDist) {
1951                 bestSnapPos = snapPos;
1952                 bestSnapPosDist = qAbs(snapPosDist);
1953             }
1954         }
1955 
1956         // the snap point interval
1957         if (snapIntervalY > 0.0) {
1958             qreal first = minPos + snapFirstY;
1959             qreal snapPos;
1960             if (dir > 0)
1961                 snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first;
1962             else if (dir < 0)
1963                 snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first;
1964             else if (p <= first)
1965                 snapPos = first;
1966             else
1967             {
1968                 qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first;
1969                 if (p >= last)
1970                     snapPos = last;
1971                 else
1972                     snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first;
1973             }
1974 
1975             if (snapPos >= first && snapPos <= maxPos ) {
1976                 qreal snapPosDist = snapPos - p;
1977 
1978                 if (qIsNaN(bestSnapPos) ||
1979                     qAbs(snapPosDist) < bestSnapPosDist) {
1980                     bestSnapPos = snapPos;
1981                     bestSnapPosDist = qAbs(snapPosDist);
1982                 }
1983             }
1984         }
1985     }
1986 
1987     return bestSnapPos;
1988 }
1989 
1990 /*!
1991     \enum QScroller::State
1992 
1993     This enum contains the different QScroller states.
1994 
1995     \value Inactive The scroller is not scrolling and nothing is pressed.
1996     \value Pressed A touch event was received or the mouse button was pressed but the scroll area is currently not dragged.
1997     \value Dragging The scroll area is currently following the touch point or mouse.
1998     \value Scrolling The scroll area is moving on it's own.
1999 */
2000 
2001 /*!
2002     \enum QScroller::ScrollerGestureType
2003 
2004     This enum contains the different gesture types that are supported by the QScroller gesture recognizer.
2005 
2006     \value TouchGesture The gesture recognizer will only trigger on touch
2007         events.  Specifically it will react on single touch points when using a
2008         touch screen and dual touch points when using a touchpad.
2009     \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events.
2010     \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events.
2011     \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events.
2012 */
2013 
2014 /*!
2015     \enum QScroller::Input
2016 
2017     This enum contains an input device agnostic view of input events that are relevant for QScroller.
2018 
2019     \value InputPress   The user pressed the input device (e.g.  QEvent::MouseButtonPress,
2020                         QEvent::GraphicsSceneMousePress, QEvent::TouchBegin)
2021 
2022     \value InputMove    The user moved the input device (e.g.  QEvent::MouseMove,
2023                         QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate)
2024 
2025     \value InputRelease The user released the input device (e.g.  QEvent::MouseButtonRelease,
2026                         QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd)
2027 
2028 */
2029 
2030 QT_END_NAMESPACE
2031 
2032 #include "moc_qscroller.cpp"
2033 #include "moc_qscroller_p.cpp"
2034