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