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

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