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