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

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