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) : 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 qreal stopProgress = progressForValue(curve: sp->scrollingCurve, value: qAbs(t: (stopPos - startPos) / deltaPos));
1304
1305 if (!canOvershoot) {
1306 qCDebug(lcScroller) << "Overshoot stopp:" << stopProgress;
1307
1308 pushSegment(type: ScrollTypeFlick, deltaTime, stopProgress, startPos, deltaPos: endPos, stopPos,
1309 curve: sp->scrollingCurve.type(), orientation);
1310 } else {
1311 qreal oDeltaTime = sp->overshootScrollTime;
1312 qreal oStopProgress = qMin(a: stopProgress + oDeltaTime * qreal(0.3) / deltaTime, b: qreal(1));
1313 qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(progress: oStopProgress) - stopPos;
1314 qreal oMaxDistance = qSign(r: oDistance) * (viewSize * sp->overshootScrollDistanceFactor);
1315
1316 qCDebug(lcScroller) << "1 oDistance:" << oDistance << "Max:" << oMaxDistance
1317 << "stopP/oStopP" << stopProgress << oStopProgress;
1318
1319 if (qAbs(t: oDistance) > qAbs(t: oMaxDistance)) {
1320 oStopProgress = progressForValue(curve: sp->scrollingCurve,
1321 value: qAbs(t: (stopPos + oMaxDistance - startPos) / deltaPos));
1322 oDistance = oMaxDistance;
1323 qCDebug(lcScroller) << "2 oDistance:" << oDistance << "Max:" << oMaxDistance
1324 << "stopP/oStopP" << stopProgress << oStopProgress;
1325 }
1326
1327 pushSegment(type: ScrollTypeFlick, deltaTime, stopProgress: oStopProgress, startPos, deltaPos,
1328 stopPos: stopPos + oDistance, curve: sp->scrollingCurve.type(), orientation);
1329 pushSegment(type: ScrollTypeOvershoot, deltaTime: oDeltaTime * qreal(0.7), stopProgress: qreal(1.0),
1330 startPos: stopPos + oDistance, deltaPos: -oDistance, stopPos, curve: sp->scrollingCurve.type(),
1331 orientation);
1332 }
1333 return;
1334 }
1335
1336 pushSegment(type: ScrollTypeFlick, deltaTime, stopProgress: qreal(1.0), startPos, deltaPos, stopPos: endPos,
1337 curve: sp->scrollingCurve.type(), orientation);
1338}
1339
1340
1341void QScrollerPrivate::createScrollingSegments(const QPointF &v,
1342 const QPointF &startPos,
1343 const QPointF &ppm)
1344{
1345 const QScrollerPropertiesPrivate *sp = properties.d.data();
1346
1347 // This is only correct for QEasingCurve::OutQuad (linear velocity,
1348 // constant deceleration), but the results look and feel ok for OutExpo
1349 // and OutSine as well
1350
1351 // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime)
1352 // v(0) = vrelease
1353 // v(deltaTime) = 0
1354 // deltaTime = (2 * vrelease) / (a * differential(0))
1355
1356 // pos(t) = integrate(v(t)dt)
1357 // pos(t) = vrelease * t - 0.5 * a * t * t
1358 // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime
1359 // deltaPos = pos(deltaTime)
1360
1361 QVector2D vel(v);
1362 qreal deltaTime = (qreal(2) * vel.length())
1363 / (sp->decelerationFactor * differentialForProgress(curve: sp->scrollingCurve, pos: 0));
1364 QPointF deltaPos = (vel.normalized() * QVector2D(ppm)).toPointF()
1365 * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor;
1366
1367 createScrollingSegments(v: v.x(), startPos: startPos.x(), deltaTime, deltaPos: deltaPos.x(),
1368 orientation: Qt::Horizontal);
1369 createScrollingSegments(v: v.y(), startPos: startPos.y(), deltaTime, deltaPos: deltaPos.y(),
1370 orientation: Qt::Vertical);
1371}
1372
1373/*! \internal
1374 Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget.
1375 Returns \c true if the scrolling was accepted and a target was returned.
1376*/
1377bool QScrollerPrivate::prepareScrolling(const QPointF &position)
1378{
1379 QScrollPrepareEvent spe(position);
1380 spe.ignore();
1381 sendEvent(o: target, e: &spe);
1382
1383 qCDebug(lcScroller) << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted()
1384 << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos();
1385 if (spe.isAccepted()) {
1386 QPointF oldContentPos = contentPosition + overshootPosition;
1387 QPointF contentDelta = spe.contentPos() - oldContentPos;
1388
1389 viewportSize = spe.viewportSize();
1390 contentPosRange = spe.contentPosRange();
1391 if (contentPosRange.width() < 0)
1392 contentPosRange.setWidth(0);
1393 if (contentPosRange.height() < 0)
1394 contentPosRange.setHeight(0);
1395 contentPosition = clampToRect(p: spe.contentPos(), rect: contentPosRange);
1396 overshootPosition = spe.contentPos() - contentPosition;
1397
1398 // - check if the content position was moved
1399 if (contentDelta != QPointF(0, 0)) {
1400 // need to correct all segments
1401 for (int i = 0; i < xSegments.size(); i++)
1402 xSegments[i].startPos -= contentDelta.x();
1403
1404 for (int i = 0; i < ySegments.size(); i++)
1405 ySegments[i].startPos -= contentDelta.y();
1406 }
1407
1408 if (QWidget *w = qobject_cast<QWidget *>(o: target))
1409 setDpiFromWidget(w);
1410#if QT_CONFIG(graphicsview)
1411 if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(object: target)) {
1412 //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
1413 if (const auto *scene = go->scene()) {
1414 const auto views = scene->views();
1415 if (!views.isEmpty())
1416 setDpiFromWidget(views.first());
1417 }
1418 }
1419#endif
1420
1421 if (state == QScroller::Scrolling) {
1422 recalcScrollingSegments();
1423 }
1424 return true;
1425 }
1426
1427 return false;
1428}
1429
1430void QScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp)
1431{
1432 const QScrollerPropertiesPrivate *sp = properties.d.data();
1433
1434 QPointF deltaPixel = position - lastPosition;
1435 qint64 deltaTime = timestamp - lastTimestamp;
1436
1437 if (sp->axisLockThreshold) {
1438 int dx = qAbs(t: deltaPixel.x());
1439 int dy = qAbs(t: deltaPixel.y());
1440 if (dx || dy) {
1441 bool vertical = (dy > dx);
1442 qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
1443 qCDebug(lcScroller) << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << sp->axisLockThreshold
1444 << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy;
1445 if (alpha <= sp->axisLockThreshold) {
1446 if (vertical)
1447 deltaPixel.setX(0);
1448 else
1449 deltaPixel.setY(0);
1450 }
1451 }
1452 }
1453
1454 // calculate velocity (if the user would release the mouse NOW)
1455 updateVelocity(deltaPixelRaw: deltaPixel, deltaTime);
1456
1457 // restrict velocity, if content is not scrollable
1458 QRectF max = contentPosRange;
1459 bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1460 bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1461
1462 if (!canScrollX) {
1463 deltaPixel.setX(0);
1464 releaseVelocity.setX(0);
1465 }
1466 if (!canScrollY) {
1467 deltaPixel.setY(0);
1468 releaseVelocity.setY(0);
1469 }
1470
1471 dragDistance += deltaPixel;
1472 lastPosition = position;
1473 lastTimestamp = timestamp;
1474}
1475
1476bool QScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp)
1477{
1478 if (prepareScrolling(position)) {
1479 const QScrollerPropertiesPrivate *sp = properties.d.data();
1480
1481 if (!contentPosRange.isNull() ||
1482 (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) ||
1483 (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) {
1484
1485 lastPosition = pressPosition = position;
1486 lastTimestamp = pressTimestamp = timestamp;
1487 setState(QScroller::Pressed);
1488 }
1489 }
1490 return false;
1491}
1492
1493bool QScrollerPrivate::releaseWhilePressed(const QPointF &, qint64)
1494{
1495 if (overshootPosition != QPointF(0.0, 0.0)) {
1496 setState(QScroller::Scrolling);
1497 return true;
1498 } else {
1499 setState(QScroller::Inactive);
1500 return false;
1501 }
1502}
1503
1504bool QScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp)
1505{
1506 Q_Q(QScroller);
1507 const QScrollerPropertiesPrivate *sp = properties.d.data();
1508 QPointF ppm = q->pixelPerMeter();
1509
1510 QPointF deltaPixel = position - pressPosition;
1511
1512 bool moveAborted = false;
1513 bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance);
1514
1515 // check the direction of the mouse drag and abort if it's too much in the wrong direction.
1516 if (moveStarted) {
1517 QRectF max = contentPosRange;
1518 bool canScrollX = (max.width() > 0);
1519 bool canScrollY = (max.height() > 0);
1520
1521 if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1522 canScrollX = true;
1523 if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1524 canScrollY = true;
1525
1526 if (qAbs(t: deltaPixel.x() / ppm.x()) < qAbs(t: deltaPixel.y() / ppm.y())) {
1527 if (!canScrollY)
1528 moveAborted = true;
1529 } else {
1530 if (!canScrollX)
1531 moveAborted = true;
1532 }
1533 }
1534
1535 if (moveAborted) {
1536 setState(QScroller::Inactive);
1537 moveStarted = false;
1538
1539 } else if (moveStarted) {
1540 setState(QScroller::Dragging);
1541
1542 // subtract the dragStartDistance
1543 deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength());
1544
1545 if (deltaPixel != QPointF(0, 0)) {
1546 // handleDrag updates lastPosition, lastTimestamp and velocity
1547 handleDrag(position: pressPosition + deltaPixel, timestamp);
1548 }
1549 }
1550 return moveStarted;
1551}
1552
1553bool QScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp)
1554{
1555 // handleDrag updates lastPosition, lastTimestamp and velocity
1556 handleDrag(position, timestamp);
1557 return true;
1558}
1559
1560void QScrollerPrivate::timerEventWhileDragging()
1561{
1562 if (dragDistance != QPointF(0, 0)) {
1563 qCDebug(lcScroller) << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance;
1564
1565 setContentPositionHelperDragging(-dragDistance);
1566 dragDistance = QPointF(0, 0);
1567 }
1568}
1569
1570bool QScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp)
1571{
1572 Q_Q(QScroller);
1573 const QScrollerPropertiesPrivate *sp = properties.d.data();
1574
1575 // handleDrag updates lastPosition, lastTimestamp and velocity
1576 handleDrag(position, timestamp);
1577
1578 // check if we moved at all - this can happen if you stop a running
1579 // scroller with a press and release shortly afterwards
1580 QPointF deltaPixel = position - pressPosition;
1581 if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) {
1582
1583 // handle accelerating flicks
1584 if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime &&
1585 ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) {
1586
1587 // - determine if the direction was changed
1588 int signX = 0, signY = 0;
1589 if (releaseVelocity.x())
1590 signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1;
1591 if (releaseVelocity.y())
1592 signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1;
1593
1594 if (signX > 0)
1595 releaseVelocity.setX(qBound(min: -sp->maximumVelocity,
1596 val: oldVelocity.x() * sp->acceleratingFlickSpeedupFactor,
1597 max: sp->maximumVelocity));
1598 if (signY > 0)
1599 releaseVelocity.setY(qBound(min: -sp->maximumVelocity,
1600 val: oldVelocity.y() * sp->acceleratingFlickSpeedupFactor,
1601 max: sp->maximumVelocity));
1602 }
1603 }
1604
1605 QPointF ppm = q->pixelPerMeter();
1606 createScrollingSegments(v: releaseVelocity, startPos: contentPosition + overshootPosition, ppm);
1607
1608 qCDebug(lcScroller) << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity
1609 << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition;
1610
1611 if (xSegments.isEmpty() && ySegments.isEmpty())
1612 setState(QScroller::Inactive);
1613 else
1614 setState(QScroller::Scrolling);
1615
1616 return true;
1617}
1618
1619void QScrollerPrivate::timerEventWhileScrolling()
1620{
1621 qCDebug(lcScroller) << "QScroller::timerEventWhileScrolling()";
1622
1623 setContentPositionHelperScrolling();
1624 if (xSegments.isEmpty() && ySegments.isEmpty())
1625 setState(QScroller::Inactive);
1626}
1627
1628bool QScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp)
1629{
1630 Q_Q(QScroller);
1631
1632 if ((q->velocity() <= properties.d->maximumClickThroughVelocity) &&
1633 (overshootPosition == QPointF(0.0, 0.0))) {
1634 setState(QScroller::Inactive);
1635 return false;
1636 } else {
1637 lastPosition = pressPosition = position;
1638 lastTimestamp = pressTimestamp = timestamp;
1639 setState(QScroller::Pressed);
1640 setState(QScroller::Dragging);
1641 return true;
1642 }
1643}
1644
1645/*! \internal
1646 This function handles all state changes of the scroller.
1647*/
1648void QScrollerPrivate::setState(QScroller::State newstate)
1649{
1650 Q_Q(QScroller);
1651 bool sendLastScroll = false;
1652 bool startTimer = false;
1653
1654 if (state == newstate)
1655 return;
1656
1657 qCDebug(lcScroller) << q << "QScroller::setState(" << stateName(state: newstate) << ')';
1658
1659 switch (newstate) {
1660 case QScroller::Inactive:
1661#if QT_CONFIG(animation)
1662 scrollTimer->stop();
1663#endif
1664
1665 // send the last scroll event (but only after the current state change was finished)
1666 if (!firstScroll)
1667 sendLastScroll = true;
1668
1669 releaseVelocity = QPointF(0, 0);
1670 break;
1671
1672 case QScroller::Pressed:
1673#if QT_CONFIG(animation)
1674 scrollTimer->stop();
1675#endif
1676
1677 oldVelocity = releaseVelocity;
1678 releaseVelocity = QPointF(0, 0);
1679 break;
1680
1681 case QScroller::Dragging:
1682 dragDistance = QPointF(0, 0);
1683#if QT_CONFIG(animation)
1684 if (state == QScroller::Pressed)
1685 startTimer = true;
1686#endif
1687 break;
1688
1689 case QScroller::Scrolling:
1690#if QT_CONFIG(animation)
1691 startTimer = true;
1692#endif
1693 break;
1694 }
1695
1696 qSwap(value1&: state, value2&: newstate);
1697
1698#if QT_CONFIG(animation)
1699 // Only start the timer after the state has been changed
1700 if (startTimer)
1701 scrollTimer->start();
1702#endif
1703 if (sendLastScroll) {
1704 QScrollEvent se(contentPosition, overshootPosition, QScrollEvent::ScrollFinished);
1705 sendEvent(o: target, e: &se);
1706 firstScroll = true;
1707 }
1708 if (state == QScroller::Dragging || state == QScroller::Scrolling) {
1709 if (!qt_activeScrollers()->contains(t: q))
1710 qt_activeScrollers()->push_back(t: q);
1711 } else {
1712 qt_activeScrollers()->removeOne(t: q);
1713 }
1714 emit q->stateChanged(newstate: state);
1715}
1716
1717
1718/*! \internal
1719 Helps when setting the content position.
1720 It will try to move the content by the requested delta but stop in case
1721 when we are coming back from an overshoot or a scrollTo.
1722 It will also indicate a new overshooting condition by the overshootX and oversthootY flags.
1723
1724 In this cases it will reset the velocity variables and other flags.
1725
1726 Also keeps track of the current over-shooting value in overshootPosition.
1727
1728 \a deltaPos is the amount of pixels the current content position should be moved
1729*/
1730void QScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos)
1731{
1732 const QScrollerPropertiesPrivate *sp = properties.d.data();
1733
1734 if (sp->overshootDragResistanceFactor)
1735 overshootPosition /= sp->overshootDragResistanceFactor;
1736
1737 QPointF oldPos = contentPosition + overshootPosition;
1738 QPointF newPos = oldPos + deltaPos;
1739
1740 qCDebug(lcScroller) << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])";
1741 qCDebug(lcScroller) << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos;
1742
1743 QPointF newClampedPos = clampToRect(p: newPos, rect: contentPosRange);
1744
1745 // --- handle overshooting and stop if the coordinate is going back inside the normal area
1746 bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1747 bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1748 bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1749 ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1750 !sp->overshootDragDistanceFactor;
1751 bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1752 ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1753 !sp->overshootDragDistanceFactor;
1754 bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width());
1755 bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height());
1756
1757 qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0;
1758 qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0;
1759
1760 qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor;
1761 qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor;
1762
1763 qCDebug(lcScroller) << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor
1764 << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy;
1765 qCDebug(lcScroller) << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX;
1766
1767 if (sp->overshootDragResistanceFactor) {
1768 newOvershootX *= sp->overshootDragResistanceFactor;
1769 newOvershootY *= sp->overshootDragResistanceFactor;
1770 }
1771
1772 // -- stop at the maximum overshoot distance
1773
1774 newOvershootX = qBound(min: -maxOvershootX, val: newOvershootX, max: maxOvershootX);
1775 newOvershootY = qBound(min: -maxOvershootY, val: newOvershootY, max: maxOvershootY);
1776
1777 overshootPosition.setX(newOvershootX);
1778 overshootPosition.setY(newOvershootY);
1779 contentPosition = newClampedPos;
1780
1781 QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated);
1782 sendEvent(o: target, e: &se);
1783 firstScroll = false;
1784
1785 qCDebug(lcScroller) << " --> new position:" << newClampedPos << "- new overshoot:"
1786 << overshootPosition << "- overshoot x/y?:" << overshootPosition;
1787}
1788
1789
1790qreal QScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos)
1791{
1792 qreal pos = oldPos;
1793
1794 // check the X segments for new positions
1795 while (!segments.isEmpty()) {
1796 const ScrollSegment s = segments.head();
1797
1798 if ((s.startTime + s.deltaTime * s.stopProgress) <= now) {
1799 segments.dequeue();
1800 pos = s.stopPos;
1801 } else if (s.startTime <= now) {
1802 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
1803 pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress);
1804 if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) {
1805 segments.dequeue();
1806 pos = s.stopPos;
1807 } else {
1808 break;
1809 }
1810 } else {
1811 break;
1812 }
1813 }
1814 return pos;
1815}
1816
1817void QScrollerPrivate::setContentPositionHelperScrolling()
1818{
1819 qint64 now = monotonicTimer.elapsed();
1820 QPointF newPos = contentPosition + overshootPosition;
1821
1822 newPos.setX(nextSegmentPosition(segments&: xSegments, now, oldPos: newPos.x()));
1823 newPos.setY(nextSegmentPosition(segments&: ySegments, now, oldPos: newPos.y()));
1824
1825 // -- set the position and handle overshoot
1826 qCDebug(lcScroller) << "QScroller::setContentPositionHelperScrolling()\n"
1827 " --> overshoot:" << overshootPosition << "- new pos:" << newPos;
1828
1829 QPointF newClampedPos = clampToRect(p: newPos, rect: contentPosRange);
1830
1831 overshootPosition = newPos - newClampedPos;
1832 contentPosition = newClampedPos;
1833
1834 QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted
1835 : QScrollEvent::ScrollUpdated);
1836 sendEvent(o: target, e: &se);
1837 firstScroll = false;
1838
1839 qCDebug(lcScroller) << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition;
1840}
1841
1842/*! \internal
1843 Returns the next snap point in direction.
1844 If \a direction >0 it will return the next snap point that is larger than the current position.
1845 If \a direction <0 it will return the next snap point that is smaller than the current position.
1846 If \a direction ==0 it will return the nearest snap point (or the current position if we are already
1847 on a snap point.
1848 Returns the nearest snap position or NaN if no such point could be found.
1849 */
1850qreal QScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation) const
1851{
1852 qreal bestSnapPos = Q_QNAN;
1853 qreal bestSnapPosDist = Q_INFINITY;
1854
1855 qreal minPos;
1856 qreal maxPos;
1857
1858 if (orientation == Qt::Horizontal) {
1859 minPos = contentPosRange.left();
1860 maxPos = contentPosRange.right();
1861 } else {
1862 minPos = contentPosRange.top();
1863 maxPos = contentPosRange.bottom();
1864 }
1865
1866 if (orientation == Qt::Horizontal) {
1867 // the snap points in the list
1868 for (qreal snapPos : snapPositionsX) {
1869 qreal snapPosDist = snapPos - p;
1870 if ((dir > 0 && snapPosDist < 0) ||
1871 (dir < 0 && snapPosDist > 0))
1872 continue; // wrong direction
1873 if (snapPos < minPos || snapPos > maxPos )
1874 continue; // invalid
1875
1876 if (qIsNaN(d: bestSnapPos) ||
1877 qAbs(t: snapPosDist) < bestSnapPosDist ) {
1878 bestSnapPos = snapPos;
1879 bestSnapPosDist = qAbs(t: snapPosDist);
1880 }
1881 }
1882
1883 // the snap point interval
1884 if (snapIntervalX > 0.0) {
1885 qreal first = minPos + snapFirstX;
1886 qreal snapPos;
1887 if (dir > 0)
1888 snapPos = qCeil(v: (p - first) / snapIntervalX) * snapIntervalX + first;
1889 else if (dir < 0)
1890 snapPos = qFloor(v: (p - first) / snapIntervalX) * snapIntervalX + first;
1891 else if (p <= first)
1892 snapPos = first;
1893 else
1894 {
1895 qreal last = qFloor(v: (maxPos - first) / snapIntervalX) * snapIntervalX + first;
1896 if (p >= last)
1897 snapPos = last;
1898 else
1899 snapPos = qRound(d: (p - first) / snapIntervalX) * snapIntervalX + first;
1900 }
1901
1902 if (snapPos >= first && snapPos <= maxPos ) {
1903 qreal snapPosDist = snapPos - p;
1904
1905 if (qIsNaN(d: bestSnapPos) ||
1906 qAbs(t: snapPosDist) < bestSnapPosDist ) {
1907 bestSnapPos = snapPos;
1908 bestSnapPosDist = qAbs(t: snapPosDist);
1909 }
1910 }
1911 }
1912
1913 } else { // (orientation == Qt::Vertical)
1914 // the snap points in the list
1915 for (qreal snapPos : snapPositionsY) {
1916 qreal snapPosDist = snapPos - p;
1917 if ((dir > 0 && snapPosDist < 0) ||
1918 (dir < 0 && snapPosDist > 0))
1919 continue; // wrong direction
1920 if (snapPos < minPos || snapPos > maxPos )
1921 continue; // invalid
1922
1923 if (qIsNaN(d: bestSnapPos) ||
1924 qAbs(t: snapPosDist) < bestSnapPosDist) {
1925 bestSnapPos = snapPos;
1926 bestSnapPosDist = qAbs(t: snapPosDist);
1927 }
1928 }
1929
1930 // the snap point interval
1931 if (snapIntervalY > 0.0) {
1932 qreal first = minPos + snapFirstY;
1933 qreal snapPos;
1934 if (dir > 0)
1935 snapPos = qCeil(v: (p - first) / snapIntervalY) * snapIntervalY + first;
1936 else if (dir < 0)
1937 snapPos = qFloor(v: (p - first) / snapIntervalY) * snapIntervalY + first;
1938 else if (p <= first)
1939 snapPos = first;
1940 else
1941 {
1942 qreal last = qFloor(v: (maxPos - first) / snapIntervalY) * snapIntervalY + first;
1943 if (p >= last)
1944 snapPos = last;
1945 else
1946 snapPos = qRound(d: (p - first) / snapIntervalY) * snapIntervalY + first;
1947 }
1948
1949 if (snapPos >= first && snapPos <= maxPos ) {
1950 qreal snapPosDist = snapPos - p;
1951
1952 if (qIsNaN(d: bestSnapPos) ||
1953 qAbs(t: snapPosDist) < bestSnapPosDist) {
1954 bestSnapPos = snapPos;
1955 bestSnapPosDist = qAbs(t: snapPosDist);
1956 }
1957 }
1958 }
1959 }
1960
1961 return bestSnapPos;
1962}
1963
1964/*!
1965 \enum QScroller::State
1966
1967 This enum contains the different QScroller states.
1968
1969 \value Inactive The scroller is not scrolling and nothing is pressed.
1970 \value Pressed A touch event was received or the mouse button was pressed
1971 but the scroll area is currently not dragged.
1972 \value Dragging The scroll area is currently following the touch point or mouse.
1973 \value Scrolling The scroll area is moving on it's own.
1974*/
1975
1976/*!
1977 \enum QScroller::ScrollerGestureType
1978
1979 This enum contains the different gesture types that are supported by the QScroller gesture recognizer.
1980
1981 \value TouchGesture The gesture recognizer will only trigger on touch
1982 events. Specifically it will react on single touch points when using a
1983 touch screen and dual touch points when using a touchpad.
1984 \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events.
1985 \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events.
1986 \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events.
1987*/
1988
1989/*!
1990 \enum QScroller::Input
1991
1992 This enum contains an input device agnostic view of input events that are relevant for QScroller.
1993
1994 \value InputPress The user pressed the input device (e.g. QEvent::MouseButtonPress,
1995 QEvent::GraphicsSceneMousePress, QEvent::TouchBegin)
1996
1997 \value InputMove The user moved the input device (e.g. QEvent::MouseMove,
1998 QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate)
1999
2000 \value InputRelease The user released the input device (e.g. QEvent::MouseButtonRelease,
2001 QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd)
2002
2003*/
2004
2005QT_END_NAMESPACE
2006
2007#include "moc_qscroller.cpp"
2008#include "moc_qscroller_p.cpp"
2009

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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