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

source code of qtbase/src/widgets/util/qscroller.cpp