Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "qevent.h"
5#include "qwidget.h"
6#include "qscroller.h"
7#include "private/qflickgesture_p.h"
8#include "private/qscroller_p.h"
10#include "private/qscrollerproperties_p.h"
11#include "qnumeric.h"
12#include "math.h"
13
14#include <QTime>
15#include <QElapsedTimer>
16#include <QMap>
17#include <QApplication>
18#include <QAbstractScrollArea>
19#if QT_CONFIG(graphicsview)
20#include <QGraphicsObject>
21#include <QGraphicsScene>
22#include <QGraphicsView>
23#endif
24#include <QVector2D>
25#include <QtCore/qmath.h>
26#include <QtGui/qevent.h>
27#include <qnumeric.h>
28
29#include <QtDebug>
30#include <QtCore/qloggingcategory.h>
31
32
34
35Q_LOGGING_CATEGORY(lcScroller, "qt.widgets.scroller")
36
38
39namespace {
41{
42 dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime << " stop progress:" << s.stopProgress;
43 dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos << " stop:" << s.stopPos;
44 dbg << "\n Curve: type:" << s.curve.type() << "\n";
45 return dbg;
46}
47} // anonymous namespace
48
49// a few helper operators to make the code below a lot more readable:
50// otherwise a lot of ifs would have to be multi-line to check both the x
51// and y coordinate separately.
52
53// returns true only if the abs. value of BOTH x and y are <= f
54inline bool operator<=(const QPointF &p, qreal f)
55{
56 return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f);
57}
58
59// returns true only if the abs. value of BOTH x and y are < f
60inline bool operator<(const QPointF &p, qreal f)
61{
62 return (qAbs(p.x()) < f) && (qAbs(p.y()) < f);
63}
64
65// returns true if the abs. value of EITHER x or y are >= f
66inline bool operator>=(const QPointF &p, qreal f)
67{
68 return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f);
69}
70
71// returns true if the abs. value of EITHER x or y are > f
72inline bool operator>(const QPointF &p, qreal f)
73{
74 return (qAbs(p.x()) > f) || (qAbs(p.y()) > f);
75}
76
77// returns a new point with both coordinates having the abs. value of the original one
78inline QPointF qAbs(const QPointF &p)
79{
80 return QPointF(qAbs(p.x()), qAbs(p.y()));
81}
82
83// returns a new point with all components of p1 multiplied by the corresponding components of p2
84inline QPointF operator*(const QPointF &p1, const QPointF &p2)
85{
86 return QPointF(p1.x() * p2.x(), p1.y() * p2.y());
87}
88
89// returns a new point with all components of p1 divided by the corresponding components of p2
90inline QPointF operator/(const QPointF &p1, const QPointF &p2)
91{
92 return QPointF(p1.x() / p2.x(), p1.y() / p2.y());
93}
94
95inline QPointF clampToRect(const QPointF &p, const QRectF &rect)
96{
97 qreal x = qBound(rect.left(), p.x(), rect.right());
98 qreal y = qBound(rect.top(), p.y(), rect.bottom());
99 return QPointF(x, y);
100}
101
102// returns -1, 0 or +1 according to r being <0, ==0 or >0
103inline int qSign(qreal r)
104{
105 return (r < 0) ? -1 : ((r > 0) ? 1 : 0);
106}
107
108// this version is not mathematically exact, but it just works for every
109// easing curve type (even custom ones)
110
112{
113 const qreal dx = 0.01;
114 qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx);
115 qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx);
116 qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx);
117
118 qCDebug(lcScroller) << "differentialForProgress(type: " << curve.type()
119 << ", pos: " << pos << ") = " << d;
120
121 return d;
122}
123
124// this version is not mathematically exact, but it just works for every
125// easing curve type (even custom ones)
126
128{
129 if (Q_UNLIKELY(curve.type() >= QEasingCurve::InElastic &&
130 curve.type() < QEasingCurve::Custom)) {
131 qWarning("progressForValue(): QEasingCurves of type %d do not have an "
132 "inverse, since they are not injective.", curve.type());
133 return value;
134 }
135 if (value < qreal(0) || value > qreal(1))
136 return value;
137
138 qreal progress = value, left(0), right(1);
139 for (int iterations = 6; iterations; --iterations) {
140 qreal v = curve.valueForProgress(progress);
141 if (v < value)
142 left = progress;
143 else if (v > value)
144 right = progress;
145 else
146 break;
147 progress = (left + right) / qreal(2);
148 }
149 return progress;
150}
151
152
153#if QT_CONFIG(animation)
154class QScrollTimer : public QAbstractAnimation
155{
156public:
157 QScrollTimer(QScrollerPrivate *_d)
158 : QAbstractAnimation(_d), d(_d), ignoreUpdate(false), skip(0)
159 { }
160
161 int duration() const override
162 {
163 return -1;
164 }
165
166 void start()
167 {
168 // QAbstractAnimation::start() will immediately call
169 // updateCurrentTime(), but our state is not set correctly yet
170 ignoreUpdate = true;
172 ignoreUpdate = false;
173 skip = 0;
174 }
175
176protected:
177 void updateCurrentTime(int /*currentTime*/) override
178 {
179 if (!ignoreUpdate) {
180 if (++skip >= d->frameRateSkip()) {
181 skip = 0;
182 d->timerTick();
183 }
184 }
185 }
186
187private:
189 bool ignoreUpdate;
190 int skip;
191};
192#endif // animation
193
232typedef QMap<QObject *, QScroller *> ScrollerHash;
233
234Q_GLOBAL_STATIC(ScrollerHash, qt_allScrollers)
235Q_GLOBAL_STATIC(QList<QScroller *>, qt_activeScrollers)
236
237
242bool QScroller::hasScroller(QObject *target)
243{
244 return (qt_allScrollers()->value(target));
245}
246
256{
257 if (!target) {
258 qWarning("QScroller::scroller() was called with a null target.");
259 return nullptr;
260 }
261
262 if (qt_allScrollers()->contains(target))
263 return qt_allScrollers()->value(target);
264
265 QScroller *s = new QScroller(target);
266 qt_allScrollers()->insert(target, s);
267 return s;
268}
269
275{
276 return scroller(const_cast<QObject*>(target));
277}
278
284QList<QScroller *> QScroller::activeScrollers()
285{
286 return *qt_activeScrollers();
287}
288
294{
295 Q_D(const QScroller);
296 return d->target;
297}
298
314{
315 Q_D(const QScroller);
316 return d->properties;
317}
318
320{
321 Q_D(QScroller);
322 if (d->properties != sp) {
323 d->properties = sp;
325
326 // we need to force the recalculation here, since the overshootPolicy may have changed and
327 // existing segments may include an overshoot animation.
328 d->recalcScrollingSegments(true);
329 }
330}
331
332#ifndef QT_NO_GESTURES
333
356{
357 // ensure that a scroller for target is created
359 if (!s)
360 return Qt::GestureType(0);
361
362 QScrollerPrivate *sp = s->d_ptr;
363 if (sp->recognizer)
364 ungrabGesture(target); // ungrab the old gesture
365
367 switch (scrollGestureType) {
371 default :
372 case TouchGesture : button = Qt::NoButton; break; // NoButton == Touch
373 }
374
375 sp->recognizer = new QFlickGestureRecognizer(button);
376 sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer);
377
378 if (target->isWidgetType()) {
379 QWidget *widget = static_cast<QWidget *>(target);
380 widget->grabGesture(sp->recognizerType);
381 if (scrollGestureType == TouchGesture)
383#if QT_CONFIG(graphicsview)
384 } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
385 if (scrollGestureType == TouchGesture)
386 go->setAcceptTouchEvents(true);
387 go->grabGesture(sp->recognizerType);
388#endif // QT_CONFIG(graphicsview)
389 }
390 return sp->recognizerType;
391}
392
400{
402 if (s && s->d_ptr)
403 return s->d_ptr->recognizerType;
404 else
405 return Qt::GestureType(0);
406}
407
415{
417 if (!s)
418 return;
419
420 QScrollerPrivate *sp = s->d_ptr;
421 if (!sp->recognizer)
422 return; // nothing to do
423
424 if (target->isWidgetType()) {
425 QWidget *widget = static_cast<QWidget *>(target);
426 widget->ungrabGesture(sp->recognizerType);
427#if QT_CONFIG(graphicsview)
428 } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
429 go->ungrabGesture(sp->recognizerType);
430#endif
431 }
432
434 // do not delete the recognizer. The QGestureManager is doing this.
435 sp->recognizer = nullptr;
436}
437
438#endif // QT_NO_GESTURES
439
443QScroller::QScroller(QObject *target)
444 : d_ptr(new QScrollerPrivate(this, target))
445{
446 Q_ASSERT(target); // you can't create a scroller without a target in any normal way
448 Q_D(QScroller);
449 d->init();
450}
451
456{
457 Q_D(QScroller);
458#ifndef QT_NO_GESTURES
460 // do not delete the recognizer. The QGestureManager is doing this.
461 d->recognizer = nullptr;
462#endif
463 qt_allScrollers()->remove(d->target);
464 qt_activeScrollers()->removeOne(this);
465
466 delete d_ptr;
467}
468
469
485{
486 Q_D(const QScroller);
487 return d->state;
488}
489
494{
495 Q_D(QScroller);
496 if (d->state != Inactive) {
497 QPointF here = clampToRect(d->contentPosition, d->contentPosRange);
498 qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal);
499 qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical);
500 QPointF snap = here;
501 if (!qIsNaN(snapX))
502 snap.setX(snapX);
503 if (!qIsNaN(snapY))
504 snap.setY(snapY);
505 d->contentPosition = snap;
506 d->overshootPosition = QPointF(0, 0);
507
508 d->setState(Inactive);
509 }
510}
511
522{
523 Q_D(const QScroller);
524 QPointF ppm = d->pixelPerMeter;
525
526#if QT_CONFIG(graphicsview)
527 if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->target)) {
528 QTransform viewtr;
529 //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
530 if (const auto *scene = go->scene()) {
531 const auto views = scene->views();
532 if (!views.isEmpty())
533 viewtr = views.first()->viewportTransform();
534 }
535 QTransform tr = go->deviceTransform(viewtr);
536 if (tr.isScaling()) {
537 QPointF p0 = tr.map(QPointF(0, 0));
538 QPointF px = tr.map(QPointF(1, 0));
539 QPointF py = tr.map(QPointF(0, 1));
540 ppm.rx() /= QLineF(p0, px).length();
541 ppm.ry() /= QLineF(p0, py).length();
542 }
543 }
544#endif // QT_CONFIG(graphicsview)
545 return ppm;
546}
547
557{
558 Q_D(const QScroller);
559 const QScrollerPropertiesPrivate *sp = d->properties.d.data();
560
561 switch (state()) {
562 case Dragging:
563 return d->releaseVelocity;
564 case Scrolling: {
565 QPointF vel;
566 qint64 now = d->monotonicTimer.elapsed();
567
568 if (!d->xSegments.isEmpty()) {
569 const QScrollerPrivate::ScrollSegment &s = d->xSegments.head();
570 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
571 qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000)
572 * sp->decelerationFactor * qreal(0.5)
573 * differentialForProgress(s.curve, progress);
574 vel.setX(v);
575 }
576
577 if (!d->ySegments.isEmpty()) {
578 const QScrollerPrivate::ScrollSegment &s = d->ySegments.head();
579 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
580 qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000)
581 * sp->decelerationFactor * qreal(0.5)
582 * differentialForProgress(s.curve, progress);
583 vel.setY(v);
584 }
585 return vel;
586 }
587 default:
588 return QPointF(0, 0);
589 }
590}
591
602{
603 Q_D(const QScroller);
604 return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
605 d->scrollingSegmentsEndPos(Qt::Vertical));
606}
607
623{
624 // we could make this adjustable via QScrollerProperties
625 scrollTo(pos, 300);
626}
627
632void QScroller::scrollTo(const QPointF &pos, int scrollTime)
633{
634 Q_D(QScroller);
635
636 if (d->state == Pressed || d->state == Dragging )
637 return;
638
639 // no need to resend a prepare event if we are already scrolling
640 if (d->state == Inactive && !d->prepareScrolling(QPointF()))
641 return;
642
643 QPointF newpos = clampToRect(pos, d->contentPosRange);
644 qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal);
645 qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical);
646 if (!qIsNaN(snapX))
647 newpos.setX(snapX);
648 if (!qIsNaN(snapY))
649 newpos.setY(snapY);
650
651 qCDebug(lcScroller) << "QScroller::scrollTo(req:" << pos << " [pix] / snap:"
652 << newpos << ", " << scrollTime << " [ms])";
653
654 if (newpos == d->contentPosition + d->overshootPosition)
655 return;
656
657 QPointF vel = velocity();
658
659 if (scrollTime < 0)
660 scrollTime = 0;
661 qreal time = qreal(scrollTime) / 1000;
662
663 d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QScrollerPrivate::ScrollTypeScrollTo);
664 d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QScrollerPrivate::ScrollTypeScrollTo);
665
666 if (!scrollTime)
667 d->setContentPositionHelperScrolling();
668 d->setState(scrollTime ? Scrolling : Inactive);
669}
670
686void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin)
687{
688 // we could make this adjustable via QScrollerProperties
689 ensureVisible(rect, xmargin, ymargin, 1000);
690}
691
696void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime)
697{
698 Q_D(QScroller);
699
700 if (d->state == Pressed || d->state == Dragging )
701 return;
702
703 if (d->state == Inactive && !d->prepareScrolling(QPointF()))
704 return;
705
706 // -- calculate the current pos (or the position after the current scroll)
707 QPointF startPos(d->scrollingSegmentsEndPos(Qt::Horizontal),
708 d->scrollingSegmentsEndPos(Qt::Vertical));
709
710 QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin,
711 rect.width() + 2 * xmargin, rect.height() + 2 * ymargin);
712
713 QSizeF visible = d->viewportSize;
714 QRectF visibleRect(startPos, visible);
715
716 qCDebug(lcScroller) << "QScroller::ensureVisible(" << rect << " [pix], " << xmargin
717 << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])";
718 qCDebug(lcScroller) << " --> content position:" << d->contentPosition;
719
720 if (visibleRect.contains(marginRect))
721 return;
722
723 QPointF newPos = startPos;
724
725 if (visibleRect.width() < rect.width()) {
726 // at least try to move the rect into view
727 if (rect.left() > visibleRect.left())
728 newPos.setX(rect.left());
729 else if (rect.right() < visibleRect.right())
730 newPos.setX(rect.right() - visible.width());
731
732 } else if (visibleRect.width() < marginRect.width()) {
733 newPos.setX(rect.center().x() - visibleRect.width() / 2);
734 } else if (marginRect.left() > visibleRect.left()) {
735 newPos.setX(marginRect.left());
736 } else if (marginRect.right() < visibleRect.right()) {
737 newPos.setX(marginRect.right() - visible.width());
738 }
739
740 if (visibleRect.height() < rect.height()) {
741 // at least try to move the rect into view
742 if (rect.top() > visibleRect.top())
743 newPos.setX(rect.top());
744 else if (rect.bottom() < visibleRect.bottom())
745 newPos.setX(rect.bottom() - visible.height());
746
747 } else if (visibleRect.height() < marginRect.height()) {
748 newPos.setY(rect.center().y() - visibleRect.height() / 2);
749 } else if (marginRect.top() > visibleRect.top()) {
750 newPos.setY(marginRect.top());
751 } else if (marginRect.bottom() < visibleRect.bottom()) {
752 newPos.setY(marginRect.bottom() - visible.height());
753 }
754
755 // clamp to maximum content position
756 newPos = clampToRect(newPos, d->contentPosRange);
757 if (newPos == startPos)
758 return;
759
760 scrollTo(newPos, scrollTime);
761}
762
771{
772 Q_D(QScroller);
773 d->prepareScrolling(d->pressPosition);
774}
775
782{
783 Q_D(QScroller);
784 d->snapPositionsX = positions;
785 d->snapIntervalX = 0.0;
786
787 d->recalcScrollingSegments();
788}
789
798{
799 Q_D(QScroller);
800 d->snapFirstX = first;
801 d->snapIntervalX = interval;
802 d->snapPositionsX.clear();
803
804 d->recalcScrollingSegments();
805}
806
813{
814 Q_D(QScroller);
815 d->snapPositionsY = positions;
816 d->snapIntervalY = 0.0;
817
818 d->recalcScrollingSegments();
819}
820
828{
829 Q_D(QScroller);
830 d->snapFirstY = first;
831 d->snapIntervalY = interval;
832 d->snapPositionsY.clear();
833
834 d->recalcScrollingSegments();
835}
836
837
838
839// -------------- private ------------
840
842 : target(_target)
843#ifndef QT_NO_GESTURES
844 , recognizer(nullptr)
845 , recognizerType(Qt::CustomGesture)
846#endif
847 , state(QScroller::Inactive)
848 , firstScroll(true)
849 , pressTimestamp(0)
850 , lastTimestamp(0)
851 , snapFirstX(-1.0)
852 , snapIntervalX(0.0)
853 , snapFirstY(-1.0)
854 , snapIntervalY(0.0)
856 , scrollTimer(new QScrollTimer(this))
857#endif
858 , q_ptr(q)
859{
861}
862
864{
865 setDpiFromWidget(nullptr);
867}
868
873
875{
876 switch (state) {
877 case QScroller::Inactive: return "inactive";
878 case QScroller::Pressed: return "pressed";
879 case QScroller::Dragging: return "dragging";
880 case QScroller::Scrolling: return "scrolling";
881 default: return "(invalid)";
882 }
883}
884
886{
887 switch (input) {
888 case QScroller::InputPress: return "press";
889 case QScroller::InputMove: return "move";
890 case QScroller::InputRelease: return "release";
891 default: return "(invalid)";
892 }
893}
894
896{
897#if QT_CONFIG(animation)
898 scrollTimer->stop();
899#endif
900 delete q_ptr;
901}
902
904{
905 struct timerevent {
907 typedef void (QScrollerPrivate::*timerhandler_t)();
908 timerhandler_t handler;
909 };
910
911 timerevent timerevents[] = {
914 };
915
916 for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) {
917 timerevent *te = timerevents + i;
918
919 if (state == te->state) {
920 (this->*te->handler)();
921 return;
922 }
923 }
924
925#if QT_CONFIG(animation)
926 scrollTimer->stop();
927#endif
928}
929
943{
944 Q_D(QScroller);
945
946 qCDebug(lcScroller) << "QScroller::handleInput(" << input << ", " << d->stateName(d->state)
947 << ", " << position << ", " << timestamp << ')';
948 struct statechange {
949 State state;
950 Input input;
951 typedef bool (QScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp);
952 inputhandler_t handler;
953 };
954
955 statechange statechanges[] = {
962 };
963
964 for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) {
965 statechange *sc = statechanges + i;
966
967 if (d->state == sc->state && input == sc->input)
968 return (d->*sc->handler)(position - d->overshootPosition, timestamp);
969 }
970 return false;
971}
972
977{
978 return pixelPerMeter * qreal(0.0254);
979}
980
988{
989 pixelPerMeter = dpi / qreal(0.0254);
990}
991
1001
1006void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime)
1007{
1008 if (deltaTime <= 0)
1009 return;
1010
1011 Q_Q(QScroller);
1012 QPointF ppm = q->pixelPerMeter();
1014 QPointF deltaPixel = deltaPixelRaw;
1015
1016 qCDebug(lcScroller) << "QScroller::updateVelocity(" << deltaPixelRaw
1017 << " [delta pix], " << deltaTime << " [delta ms])";
1018
1019 // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms)
1020 if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5))
1021 deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength();
1022
1023 QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm;
1024 // around 95% of all updates are in the [1..50] ms range, so make sure
1025 // to scale the smoothing factor over that range: this way a 50ms update
1026 // will have full impact, while 5ms update will only have a 10% impact.
1027 qreal smoothing = sp->dragVelocitySmoothingFactor * qMin(qreal(deltaTime), qreal(50)) / qreal(50);
1028
1029 // only smooth if we already have a release velocity and only if the
1030 // user hasn't stopped to move his finger for more than 100ms
1031 if ((releaseVelocity != QPointF(0, 0)) && (deltaTime < 100)) {
1032 qCDebug(lcScroller) << "SMOOTHED from " << newv << " to "
1033 << newv * smoothing + releaseVelocity * (qreal(1) - smoothing);
1034 // smooth x or y only if the new velocity is either 0 or at least in
1035 // the same direction of the release velocity
1036 if (!newv.x() || (qSign(releaseVelocity.x()) == qSign(newv.x())))
1037 newv.setX(newv.x() * smoothing + releaseVelocity.x() * (qreal(1) - smoothing));
1038 if (!newv.y() || (qSign(releaseVelocity.y()) == qSign(newv.y())))
1039 newv.setY(newv.y() * smoothing + releaseVelocity.y() * (qreal(1) - smoothing));
1040 } else
1041 qCDebug(lcScroller) << "NO SMOOTHING to " << newv;
1042
1043 releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity));
1044 releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity));
1045
1046 qCDebug(lcScroller) << " --> new velocity:" << releaseVelocity;
1047}
1048
1050 qreal startPos, qreal deltaPos, qreal stopPos,
1051 QEasingCurve::Type curve, Qt::Orientation orientation)
1052{
1053 if (startPos == stopPos || deltaPos == 0)
1054 return;
1055
1057 if (orientation == Qt::Horizontal && !xSegments.isEmpty()) {
1058 const auto &lastX = xSegments.constLast();
1059 s.startTime = lastX.startTime + lastX.deltaTime * lastX.stopProgress;
1060 } else if (orientation == Qt::Vertical && !ySegments.isEmpty()) {
1061 const auto &lastY = ySegments.constLast();
1062 s.startTime = lastY.startTime + lastY.deltaTime * lastY.stopProgress;
1063 } else {
1064 s.startTime = monotonicTimer.elapsed();
1065 }
1066
1067 s.startPos = startPos;
1068 s.deltaPos = deltaPos;
1069 s.stopPos = stopPos;
1070 s.deltaTime = deltaTime * 1000;
1071 s.stopProgress = stopProgress;
1072 s.curve.setType(curve);
1073 s.type = type;
1074
1075 if (orientation == Qt::Horizontal)
1076 xSegments.enqueue(s);
1077 else
1078 ySegments.enqueue(s);
1079
1080 qCDebug(lcScroller) << "+++ Added a new ScrollSegment: " << s;
1081}
1082
1083
1088{
1089 Q_Q(QScroller);
1090 QPointF ppm = q->pixelPerMeter();
1091
1092 releaseVelocity = q->velocity();
1093
1094 if (forceRecalc ||
1098}
1099
1104{
1105 if (orientation == Qt::Horizontal) {
1106 if (xSegments.isEmpty())
1107 return contentPosition.x() + overshootPosition.x();
1108 else
1109 return xSegments.last().stopPos;
1110 } else {
1111 if (ySegments.isEmpty())
1112 return contentPosition.y() + overshootPosition.y();
1113 else
1114 return ySegments.last().stopPos;
1115 }
1116}
1117
1122{
1123 const QQueue<ScrollSegment> *segments;
1124 qreal minPos;
1125 qreal maxPos;
1126
1127 if (orientation == Qt::Horizontal) {
1129 minPos = contentPosRange.left();
1130 maxPos = contentPosRange.right();
1131 } else {
1133 minPos = contentPosRange.top();
1134 maxPos = contentPosRange.bottom();
1135 }
1136
1137 if (segments->isEmpty())
1138 return true;
1139
1140 const ScrollSegment &last = segments->last();
1141 qreal stopPos = last.stopPos;
1142
1143 if (last.type == ScrollTypeScrollTo)
1144 return true; // scrollTo is always valid
1145
1146 if (last.type == ScrollTypeOvershoot &&
1147 (stopPos != minPos && stopPos != maxPos))
1148 return false;
1149
1150 if (stopPos < minPos || stopPos > maxPos)
1151 return false;
1152
1153 if (stopPos == minPos || stopPos == maxPos) // the begin and the end of the list are always ok
1154 return true;
1155
1156 qreal nextSnap = nextSnapPos(stopPos, 0, orientation);
1157 if (!qIsNaN(nextSnap) && stopPos != nextSnap)
1158 return false;
1159
1160 return true;
1161}
1162
1167 Qt::Orientation orientation, ScrollType type)
1168{
1169 Q_UNUSED(v);
1170
1171 if (orientation == Qt::Horizontal)
1172 xSegments.clear();
1173 else
1174 ySegments.clear();
1175
1176 qCDebug(lcScroller) << "+++ createScrollToSegments: t:" << deltaTime << "ep:"
1177 << endPos << "o:" << int(orientation);
1178
1180
1181 qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x()
1183 qreal deltaPos = (endPos - startPos) / 2;
1184
1185 pushSegment(type, deltaTime * qreal(0.3), qreal(1.0), startPos, deltaPos, startPos + deltaPos,
1186 QEasingCurve::InQuad, orientation);
1187 pushSegment(type, deltaTime * qreal(0.7), qreal(1.0), startPos + deltaPos, deltaPos, endPos,
1188 sp->scrollingCurve.type(), orientation);
1189}
1190
1194 qreal deltaTime, qreal deltaPos,
1195 Qt::Orientation orientation)
1196{
1198
1200 qreal minPos;
1201 qreal maxPos;
1202 qreal viewSize;
1203
1204 if (orientation == Qt::Horizontal) {
1205 xSegments.clear();
1206 policy = sp->hOvershootPolicy;
1207 minPos = contentPosRange.left();
1208 maxPos = contentPosRange.right();
1209 viewSize = viewportSize.width();
1210 } else {
1211 ySegments.clear();
1212 policy = sp->vOvershootPolicy;
1213 minPos = contentPosRange.top();
1214 maxPos = contentPosRange.bottom();
1215 viewSize = viewportSize.height();
1216 }
1217
1218 bool alwaysOvershoot = (policy == QScrollerProperties::OvershootAlwaysOn);
1219 bool noOvershoot = (policy == QScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor;
1220 bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos);
1221
1222 qCDebug(lcScroller) << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos
1223 << "o:" << int(orientation);
1224
1225 qCDebug(lcScroller) << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor
1226 << ", curveType = " << sp->scrollingCurve.type();
1227
1228 qreal endPos = startPos + deltaPos;
1229
1230 qCDebug(lcScroller) << " Real Delta:" << deltaPos;
1231
1232 // -- check if are in overshoot and end in overshoot
1233 if ((startPos < minPos && endPos < minPos) ||
1234 (startPos > maxPos && endPos > maxPos)) {
1235 qreal stopPos = endPos < minPos ? minPos : maxPos;
1236 qreal oDeltaTime = sp->overshootScrollTime;
1237
1238 pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), startPos,
1239 stopPos - startPos, stopPos, sp->scrollingCurve.type(), orientation);
1240 return;
1241 }
1242
1243 // -- determine snap points
1244 qreal nextSnap = nextSnapPos(endPos, 0, orientation);
1245 qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation);
1246 qreal higherSnapPos = nextSnapPos(startPos, 1, orientation);
1247
1248 qCDebug(lcScroller) << " Real Delta:" << lowerSnapPos << '-' << nextSnap << '-' <<higherSnapPos;
1249
1250 // - check if we can reach another snap point
1251 if (nextSnap > higherSnapPos || qIsNaN(higherSnapPos))
1252 higherSnapPos = nextSnap;
1253 if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos))
1254 lowerSnapPos = nextSnap;
1255
1256 if (qAbs(v) < sp->minimumVelocity) {
1257
1258 qCDebug(lcScroller) << "### below minimum Vel" << orientation;
1259
1260 // - no snap points or already at one
1261 if (qIsNaN(nextSnap) || nextSnap == startPos)
1262 return; // nothing to do, no scrolling needed.
1263
1264 // - decide which point to use
1265
1266 qreal snapDistance = higherSnapPos - lowerSnapPos;
1267
1268 qreal pressDistance = (orientation == Qt::Horizontal) ?
1271
1272 // if not dragged far enough, pick the next snap point.
1273 if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance)
1274 endPos = nextSnap;
1275 else if (pressDistance < 0.0)
1276 endPos = lowerSnapPos;
1277 else
1278 endPos = higherSnapPos;
1279
1280 deltaPos = endPos - startPos;
1281 qreal midPos = startPos + deltaPos * qreal(0.3);
1282 pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.3), qreal(1.0), startPos,
1283 midPos - startPos, midPos, QEasingCurve::InQuad, orientation);
1284 pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.7), qreal(1.0), midPos,
1285 endPos - midPos, endPos, sp->scrollingCurve.type(), orientation);
1286 return;
1287 }
1288
1289 // - go to the next snappoint if there is one
1290 if (v > 0 && !qIsNaN(higherSnapPos)) {
1291 // change the time in relation to the changed end position
1292 if (endPos - startPos)
1293 deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos));
1294 if (deltaTime > sp->snapTime)
1295 deltaTime = sp->snapTime;
1296 endPos = higherSnapPos;
1297
1298 } else if (v < 0 && !qIsNaN(lowerSnapPos)) {
1299 // change the time in relation to the changed end position
1300 if (endPos - startPos)
1301 deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos));
1302 if (deltaTime > sp->snapTime)
1303 deltaTime = sp->snapTime;
1304 endPos = lowerSnapPos;
1305
1306 // -- check if we are overshooting
1307 } else if (endPos < minPos || endPos > maxPos) {
1308 qreal stopPos = endPos < minPos ? minPos : maxPos;
1309
1310 qCDebug(lcScroller) << "Overshoot: delta:" << (stopPos - startPos);
1311
1312 qreal stopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos));
1313
1314 if (!canOvershoot) {
1315 qCDebug(lcScroller) << "Overshoot stopp:" << stopProgress;
1316
1317 pushSegment(ScrollTypeFlick, deltaTime, stopProgress, startPos, endPos, stopPos,
1318 sp->scrollingCurve.type(), orientation);
1319 } else {
1320 qreal oDeltaTime = sp->overshootScrollTime;
1321 qreal oStopProgress = qMin(stopProgress + oDeltaTime * qreal(0.3) / deltaTime, qreal(1));
1322 qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(oStopProgress) - stopPos;
1323 qreal oMaxDistance = qSign(oDistance) * (viewSize * sp->overshootScrollDistanceFactor);
1324
1325 qCDebug(lcScroller) << "1 oDistance:" << oDistance << "Max:" << oMaxDistance
1326 << "stopP/oStopP" << stopProgress << oStopProgress;
1327
1328 if (qAbs(oDistance) > qAbs(oMaxDistance)) {
1329 oStopProgress = progressForValue(sp->scrollingCurve,
1330 qAbs((stopPos + oMaxDistance - startPos) / deltaPos));
1331 oDistance = oMaxDistance;
1332 qCDebug(lcScroller) << "2 oDistance:" << oDistance << "Max:" << oMaxDistance
1333 << "stopP/oStopP" << stopProgress << oStopProgress;
1334 }
1335
1336 pushSegment(ScrollTypeFlick, deltaTime, oStopProgress, startPos, deltaPos,
1337 stopPos + oDistance, sp->scrollingCurve.type(), orientation);
1338 pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0),
1339 stopPos + oDistance, -oDistance, stopPos, sp->scrollingCurve.type(),
1340 orientation);
1341 }
1342 return;
1343 }
1344
1345 pushSegment(ScrollTypeFlick, deltaTime, qreal(1.0), startPos, deltaPos, endPos,
1346 sp->scrollingCurve.type(), orientation);
1347}
1348
1349
1351 const QPointF &startPos,
1352 const QPointF &ppm)
1353{
1355
1356 // This is only correct for QEasingCurve::OutQuad (linear velocity,
1357 // constant deceleration), but the results look and feel ok for OutExpo
1358 // and OutSine as well
1359
1360 // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime)
1361 // v(0) = vrelease
1362 // v(deltaTime) = 0
1363 // deltaTime = (2 * vrelease) / (a * differential(0))
1364
1365 // pos(t) = integrate(v(t)dt)
1366 // pos(t) = vrelease * t - 0.5 * a * t * t
1367 // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime
1368 // deltaPos = pos(deltaTime)
1369
1370 QVector2D vel(v);
1371 qreal deltaTime = (qreal(2) * vel.length())
1372 / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0));
1373 QPointF deltaPos = (vel.normalized() * QVector2D(ppm)).toPointF()
1374 * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor;
1375
1376 createScrollingSegments(v.x(), startPos.x(), deltaTime, deltaPos.x(),
1378 createScrollingSegments(v.y(), startPos.y(), deltaTime, deltaPos.y(),
1379 Qt::Vertical);
1380}
1381
1387{
1389 spe.ignore();
1390 sendEvent(target, &spe);
1391
1392 qCDebug(lcScroller) << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted()
1393 << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos();
1394 if (spe.isAccepted()) {
1395 QPointF oldContentPos = contentPosition + overshootPosition;
1396 QPointF contentDelta = spe.contentPos() - oldContentPos;
1397
1398 viewportSize = spe.viewportSize();
1399 contentPosRange = spe.contentPosRange();
1400 if (contentPosRange.width() < 0)
1402 if (contentPosRange.height() < 0)
1404 contentPosition = clampToRect(spe.contentPos(), contentPosRange);
1405 overshootPosition = spe.contentPos() - contentPosition;
1406
1407 // - check if the content position was moved
1408 if (contentDelta != QPointF(0, 0)) {
1409 // need to correct all segments
1410 for (int i = 0; i < xSegments.size(); i++)
1411 xSegments[i].startPos -= contentDelta.x();
1412
1413 for (int i = 0; i < ySegments.size(); i++)
1414 ySegments[i].startPos -= contentDelta.y();
1415 }
1416
1417 if (QWidget *w = qobject_cast<QWidget *>(target))
1419#if QT_CONFIG(graphicsview)
1420 if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(target)) {
1421 //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
1422 if (const auto *scene = go->scene()) {
1423 const auto views = scene->views();
1424 if (!views.isEmpty())
1425 setDpiFromWidget(views.first());
1426 }
1427 }
1428#endif
1429
1430 if (state == QScroller::Scrolling) {
1432 }
1433 return true;
1434 }
1435
1436 return false;
1437}
1438
1440{
1442
1443 QPointF deltaPixel = position - lastPosition;
1444 qint64 deltaTime = timestamp - lastTimestamp;
1445
1446 if (sp->axisLockThreshold) {
1447 int dx = qAbs(deltaPixel.x());
1448 int dy = qAbs(deltaPixel.y());
1449 if (dx || dy) {
1450 bool vertical = (dy > dx);
1451 qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
1452 qCDebug(lcScroller) << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << sp->axisLockThreshold
1453 << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy;
1454 if (alpha <= sp->axisLockThreshold) {
1455 if (vertical)
1456 deltaPixel.setX(0);
1457 else
1458 deltaPixel.setY(0);
1459 }
1460 }
1461 }
1462
1463 // calculate velocity (if the user would release the mouse NOW)
1464 updateVelocity(deltaPixel, deltaTime);
1465
1466 // restrict velocity, if content is not scrollable
1467 QRectF max = contentPosRange;
1468 bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1469 bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1470
1471 if (!canScrollX) {
1472 deltaPixel.setX(0);
1474 }
1475 if (!canScrollY) {
1476 deltaPixel.setY(0);
1478 }
1479
1480 dragDistance += deltaPixel;
1482 lastTimestamp = timestamp;
1483}
1484
1486{
1489
1490 if (!contentPosRange.isNull() ||
1491 (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) ||
1492 (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) {
1493
1495 lastTimestamp = pressTimestamp = timestamp;
1497 }
1498 }
1499 return false;
1500}
1501
1503{
1504 if (overshootPosition != QPointF(0.0, 0.0)) {
1506 return true;
1507 } else {
1509 return false;
1510 }
1511}
1512
1514{
1515 Q_Q(QScroller);
1517 QPointF ppm = q->pixelPerMeter();
1518
1519 QPointF deltaPixel = position - pressPosition;
1520
1521 bool moveAborted = false;
1522 bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance);
1523
1524 // check the direction of the mouse drag and abort if it's too much in the wrong direction.
1525 if (moveStarted) {
1526 QRectF max = contentPosRange;
1527 bool canScrollX = (max.width() > 0);
1528 bool canScrollY = (max.height() > 0);
1529
1530 if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1531 canScrollX = true;
1532 if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)
1533 canScrollY = true;
1534
1535 if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) {
1536 if (!canScrollY)
1537 moveAborted = true;
1538 } else {
1539 if (!canScrollX)
1540 moveAborted = true;
1541 }
1542 }
1543
1544 if (moveAborted) {
1546 moveStarted = false;
1547
1548 } else if (moveStarted) {
1550
1551 // subtract the dragStartDistance
1552 deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength());
1553
1554 if (deltaPixel != QPointF(0, 0)) {
1555 // handleDrag updates lastPosition, lastTimestamp and velocity
1556 handleDrag(pressPosition + deltaPixel, timestamp);
1557 }
1558 }
1559 return moveStarted;
1560}
1561
1563{
1564 // handleDrag updates lastPosition, lastTimestamp and velocity
1565 handleDrag(position, timestamp);
1566 return true;
1567}
1568
1570{
1571 if (dragDistance != QPointF(0, 0)) {
1572 qCDebug(lcScroller) << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance;
1573
1575 dragDistance = QPointF(0, 0);
1576 }
1577}
1578
1580{
1581 Q_Q(QScroller);
1583
1584 // handleDrag updates lastPosition, lastTimestamp and velocity
1585 handleDrag(position, timestamp);
1586
1587 // check if we moved at all - this can happen if you stop a running
1588 // scroller with a press and release shortly afterwards
1589 QPointF deltaPixel = position - pressPosition;
1590 if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) {
1591
1592 // handle accelerating flicks
1593 if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime &&
1594 ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) {
1595
1596 // - determine if the direction was changed
1597 int signX = 0, signY = 0;
1598 if (releaseVelocity.x())
1599 signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1;
1600 if (releaseVelocity.y())
1601 signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1;
1602
1603 if (signX > 0)
1604 releaseVelocity.setX(qBound(-sp->maximumVelocity,
1605 oldVelocity.x() * sp->acceleratingFlickSpeedupFactor,
1606 sp->maximumVelocity));
1607 if (signY > 0)
1608 releaseVelocity.setY(qBound(-sp->maximumVelocity,
1609 oldVelocity.y() * sp->acceleratingFlickSpeedupFactor,
1610 sp->maximumVelocity));
1611 }
1612 }
1613
1614 QPointF ppm = q->pixelPerMeter();
1616
1617 qCDebug(lcScroller) << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity
1618 << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition;
1619
1620 if (xSegments.isEmpty() && ySegments.isEmpty())
1622 else
1624
1625 return true;
1626}
1627
1629{
1630 qCDebug(lcScroller) << "QScroller::timerEventWhileScrolling()";
1631
1633 if (xSegments.isEmpty() && ySegments.isEmpty())
1635}
1636
1638{
1639 Q_Q(QScroller);
1640
1641 if ((q->velocity() <= properties.d->maximumClickThroughVelocity) &&
1642 (overshootPosition == QPointF(0.0, 0.0))) {
1644 return false;
1645 } else {
1647 lastTimestamp = pressTimestamp = timestamp;
1650 return true;
1651 }
1652}
1653
1658{
1659 Q_Q(QScroller);
1660 bool sendLastScroll = false;
1661
1662 if (state == newstate)
1663 return;
1664
1665 qCDebug(lcScroller) << q << "QScroller::setState(" << stateName(newstate) << ')';
1666
1667 switch (newstate) {
1669#if QT_CONFIG(animation)
1670 scrollTimer->stop();
1671#endif
1672
1673 // send the last scroll event (but only after the current state change was finished)
1674 if (!firstScroll)
1675 sendLastScroll = true;
1676
1677 releaseVelocity = QPointF(0, 0);
1678 break;
1679
1680 case QScroller::Pressed:
1681#if QT_CONFIG(animation)
1682 scrollTimer->stop();
1683#endif
1684
1686 releaseVelocity = QPointF(0, 0);
1687 break;
1688
1690 dragDistance = QPointF(0, 0);
1691#if QT_CONFIG(animation)
1693 scrollTimer->start();
1694#endif
1695 break;
1696
1698#if QT_CONFIG(animation)
1699 scrollTimer->start();
1700#endif
1701 break;
1702 }
1703
1704 qSwap(state, newstate);
1705
1706 if (sendLastScroll) {
1708 sendEvent(target, &se);
1709 firstScroll = true;
1710 }
1712 if (!qt_activeScrollers()->contains(q))
1713 qt_activeScrollers()->push_back(q);
1714 } else {
1715 qt_activeScrollers()->removeOne(q);
1716 }
1717 emit q->stateChanged(state);
1718}
1719
1720
1734{
1736
1737 if (sp->overshootDragResistanceFactor)
1738 overshootPosition /= sp->overshootDragResistanceFactor;
1739
1741 QPointF newPos = oldPos + deltaPos;
1742
1743 qCDebug(lcScroller) << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])";
1744 qCDebug(lcScroller) << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos;
1745
1746 QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1747
1748 // --- handle overshooting and stop if the coordinate is going back inside the normal area
1749 bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1750 bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn);
1751 bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1752 ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1753 !sp->overshootDragDistanceFactor;
1754 bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) ||
1755 ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1756 !sp->overshootDragDistanceFactor;
1757 bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width());
1758 bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height());
1759
1760 qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0;
1761 qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0;
1762
1763 qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor;
1764 qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor;
1765
1766 qCDebug(lcScroller) << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor
1767 << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy;
1768 qCDebug(lcScroller) << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX;
1769
1770 if (sp->overshootDragResistanceFactor) {
1771 newOvershootX *= sp->overshootDragResistanceFactor;
1772 newOvershootY *= sp->overshootDragResistanceFactor;
1773 }
1774
1775 // -- stop at the maximum overshoot distance
1776
1777 newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX);
1778 newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY);
1779
1780 overshootPosition.setX(newOvershootX);
1781 overshootPosition.setY(newOvershootY);
1782 contentPosition = newClampedPos;
1783
1785 sendEvent(target, &se);
1786 firstScroll = false;
1787
1788 qCDebug(lcScroller) << " --> new position:" << newClampedPos << "- new overshoot:"
1789 << overshootPosition << "- overshoot x/y?:" << overshootPosition;
1790}
1791
1792
1794{
1795 qreal pos = oldPos;
1796
1797 // check the X segments for new positions
1798 while (!segments.isEmpty()) {
1799 const ScrollSegment s = segments.head();
1800
1801 if ((s.startTime + s.deltaTime * s.stopProgress) <= now) {
1802 segments.dequeue();
1803 pos = s.stopPos;
1804 } else if (s.startTime <= now) {
1805 qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
1806 pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress);
1807 if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) {
1808 segments.dequeue();
1809 pos = s.stopPos;
1810 } else {
1811 break;
1812 }
1813 } else {
1814 break;
1815 }
1816 }
1817 return pos;
1818}
1819
1821{
1824
1825 newPos.setX(nextSegmentPosition(xSegments, now, newPos.x()));
1826 newPos.setY(nextSegmentPosition(ySegments, now, newPos.y()));
1827
1828 // -- set the position and handle overshoot
1829 qCDebug(lcScroller) << "QScroller::setContentPositionHelperScrolling()\n"
1830 " --> overshoot:" << overshootPosition << "- new pos:" << newPos;
1831
1832 QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1833
1834 overshootPosition = newPos - newClampedPos;
1835 contentPosition = newClampedPos;
1836
1839 sendEvent(target, &se);
1840 firstScroll = false;
1841
1842 qCDebug(lcScroller) << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition;
1843}
1844
1854{
1855 qreal bestSnapPos = Q_QNAN;
1856 qreal bestSnapPosDist = Q_INFINITY;
1857
1858 qreal minPos;
1859 qreal maxPos;
1860
1861 if (orientation == Qt::Horizontal) {
1862 minPos = contentPosRange.left();
1863 maxPos = contentPosRange.right();
1864 } else {
1865 minPos = contentPosRange.top();
1866 maxPos = contentPosRange.bottom();
1867 }
1868
1869 if (orientation == Qt::Horizontal) {
1870 // the snap points in the list
1871 for (qreal snapPos : snapPositionsX) {
1872 qreal snapPosDist = snapPos - p;
1873 if ((dir > 0 && snapPosDist < 0) ||
1874 (dir < 0 && snapPosDist > 0))
1875 continue; // wrong direction
1876 if (snapPos < minPos || snapPos > maxPos )
1877 continue; // invalid
1878
1879 if (qIsNaN(bestSnapPos) ||
1880 qAbs(snapPosDist) < bestSnapPosDist ) {
1881 bestSnapPos = snapPos;
1882 bestSnapPosDist = qAbs(snapPosDist);
1883 }
1884 }
1885
1886 // the snap point interval
1887 if (snapIntervalX > 0.0) {
1888 qreal first = minPos + snapFirstX;
1889 qreal snapPos;
1890 if (dir > 0)
1891 snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first;
1892 else if (dir < 0)
1893 snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first;
1894 else if (p <= first)
1895 snapPos = first;
1896 else
1897 {
1898 qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first;
1899 if (p >= last)
1900 snapPos = last;
1901 else
1902 snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first;
1903 }
1904
1905 if (snapPos >= first && snapPos <= maxPos ) {
1906 qreal snapPosDist = snapPos - p;
1907
1908 if (qIsNaN(bestSnapPos) ||
1909 qAbs(snapPosDist) < bestSnapPosDist ) {
1910 bestSnapPos = snapPos;
1911 bestSnapPosDist = qAbs(snapPosDist);
1912 }
1913 }
1914 }
1915
1916 } else { // (orientation == Qt::Vertical)
1917 // the snap points in the list
1918 for (qreal snapPos : snapPositionsY) {
1919 qreal snapPosDist = snapPos - p;
1920 if ((dir > 0 && snapPosDist < 0) ||
1921 (dir < 0 && snapPosDist > 0))
1922 continue; // wrong direction
1923 if (snapPos < minPos || snapPos > maxPos )
1924 continue; // invalid
1925
1926 if (qIsNaN(bestSnapPos) ||
1927 qAbs(snapPosDist) < bestSnapPosDist) {
1928 bestSnapPos = snapPos;
1929 bestSnapPosDist = qAbs(snapPosDist);
1930 }
1931 }
1932
1933 // the snap point interval
1934 if (snapIntervalY > 0.0) {
1935 qreal first = minPos + snapFirstY;
1936 qreal snapPos;
1937 if (dir > 0)
1938 snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first;
1939 else if (dir < 0)
1940 snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first;
1941 else if (p <= first)
1942 snapPos = first;
1943 else
1944 {
1945 qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first;
1946 if (p >= last)
1947 snapPos = last;
1948 else
1949 snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first;
1950 }
1951
1952 if (snapPos >= first && snapPos <= maxPos ) {
1953 qreal snapPosDist = snapPos - p;
1954
1955 if (qIsNaN(bestSnapPos) ||
1956 qAbs(snapPosDist) < bestSnapPosDist) {
1957 bestSnapPos = snapPos;
1958 bestSnapPosDist = qAbs(snapPosDist);
1959 }
1960 }
1961 }
1962 }
1963
1964 return bestSnapPos;
1965}
1966
2009
2010#include "moc_qscroller.cpp"
2011#include "moc_qscroller_p.cpp"
void start(QAbstractAnimation::DeletionPolicy policy=KeepWhenStopped)
Starts the animation.
\inmodule QtCore
\inmodule QtCore
Type type() const
Returns the type of the easing curve.
Type
The type of easing curve.
qreal valueForProgress(qreal progress) const
Return the effective progress for the easing curve at progress.
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
\inmodule QtCore
Definition qcoreevent.h:45
static void unregisterRecognizer(Qt::GestureType type)
Unregisters all gesture recognizers of the specified type.
static Qt::GestureType registerRecognizer(QGestureRecognizer *recognizer)
Registers the given recognizer in the gesture framework and returns a gesture ID for it.
The QGraphicsObject class provides a base class for all graphics items that require signals,...
QList< QGraphicsView * > views() const
Returns a list of all the views that display this scene.
QTransform viewportTransform() const
Returns a matrix that maps scene coordinates to viewport coordinates.
QScreen * primaryScreen
the primary (or default) screen of the application.
\inmodule QtCore\compares equality \compareswith equality QLine \endcompareswith
Definition qline.h:192
qreal length() const
Returns the length of the line.
Definition qline.cpp:548
T & first()
Definition qlist.h:645
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void setParent(QObject *parent)
Makes the object a child of parent.
Definition qobject.cpp:2195
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
constexpr void setY(qreal y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
Definition qpoint.h:358
constexpr void setX(qreal x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
Definition qpoint.h:353
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:500
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:729
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:497
constexpr void setWidth(qreal w) noexcept
Sets the width of the rectangle to the given finite width.
Definition qrect.h:818
constexpr bool isNull() const noexcept
Returns true if the rectangle is a null rectangle, otherwise returns false.
Definition qrect.h:658
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:498
constexpr void setHeight(qreal h) noexcept
Sets the height of the rectangle to the given finite height.
Definition qrect.h:821
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:499
constexpr void setX(qreal pos) noexcept
Sets the left edge of the rectangle to the given finite x coordinate.
Definition qrect.h:508
T * data() const noexcept
Returns the value of the pointer referenced by this object.
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
qreal physicalDotsPerInchY
the number of physical dots or pixels per inch in the vertical direction
Definition qscreen.h:54
qreal physicalDotsPerInchX
the number of physical dots or pixels per inch in the horizontal direction
Definition qscreen.h:52
The QScrollEvent class is sent when scrolling.
Definition qevent.h:976
@ ScrollFinished
Definition qevent.h:983
@ ScrollStarted
Definition qevent.h:981
@ ScrollUpdated
Definition qevent.h:982
The QScrollPrepareEvent class is sent in preparation of scrolling.
Definition qevent.h:952
QPointF pixelPerMeter
qreal scrollingSegmentsEndPos(Qt::Orientation orientation) const
QQueue< ScrollSegment > ySegments
QScrollerProperties properties
QPointF pressPosition
bool moveWhilePressed(const QPointF &position, qint64 timestamp)
void setContentPositionHelperScrolling()
QList< qreal > snapPositionsY
bool pressWhileInactive(const QPointF &position, qint64 timestamp)
void setState(QScroller::State s)
qreal nextSnapPos(qreal p, int dir, Qt::Orientation orientation) const
QScroller::State state
bool pressWhileScrolling(const QPointF &position, qint64 timestamp)
QPointF contentPosition
void setDpi(const QPointF &dpi)
void recalcScrollingSegments(bool forceRecalc=false)
void setDpiFromWidget(QWidget *widget)
bool scrollingSegmentsValid(Qt::Orientation orientation) const
QElapsedTimer monotonicTimer
static const char * inputName(QScroller::Input input)
void createScrollingSegments(qreal v, qreal startPos, qreal deltaTime, qreal deltaPos, Qt::Orientation orientation)
void setContentPositionHelperDragging(const QPointF &deltaPos)
void pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation)
QPointF dpi() const
QList< qreal > snapPositionsX
QScroller * q_ptr
bool moveWhileDragging(const QPointF &position, qint64 timestamp)
QQueue< ScrollSegment > xSegments
void createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type)
bool prepareScrolling(const QPointF &position)
QPointF releaseVelocity
QPointF overshootPosition
void handleDrag(const QPointF &position, qint64 timestamp)
void sendEvent(QObject *o, QEvent *e)
bool releaseWhilePressed(const QPointF &position, qint64 timestamp)
void timerEventWhileScrolling()
void timerEventWhileDragging()
static qreal nextSegmentPosition(QQueue< ScrollSegment > &segments, qint64 now, qreal oldPos)
bool releaseWhileDragging(const QPointF &position, qint64 timestamp)
void updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime)
static const char * stateName(QScroller::State state)
QScrollerPrivate(QScroller *q, QObject *target)
The QScrollerProperties class stores the settings for a QScroller.
QScopedPointer< QScrollerPropertiesPrivate > d
OvershootPolicy
This enum describes the various modes of overshooting.
The QScroller class enables kinetic scrolling for any scrolling widget or graphics item.
Definition qscroller.h:26
void setSnapPositionsY(const QList< qreal > &positions)
Set the snap positions for the vertical axis to a list of positions.
static QScroller * scroller(QObject *target)
Returns the scroller for the given target.
QPointF velocity() const
Returns the current scrolling velocity in meter per second when the state is Scrolling or Dragging.
static void ungrabGesture(QObject *target)
Ungrabs the gesture for the target.
static Qt::GestureType grabbedGesture(QObject *target)
Returns the gesture type currently grabbed for the target or 0 if no gesture is grabbed.
void setSnapPositionsX(const QList< qreal > &positions)
Set the snap positions for the horizontal axis to a list of positions.
Input
This enum contains an input device agnostic view of input events that are relevant for QScroller.
Definition qscroller.h:51
@ InputRelease
Definition qscroller.h:54
QPointF pixelPerMeter() const
Returns the pixel per meter metric for the scrolled widget.
QPointF finalPosition() const
Returns the estimated final position for the current scroll movement.
friend class QFlickGestureRecognizer
Definition qscroller.h:110
virtual ~QScroller()
static QList< QScroller * > activeScrollers()
Returns an application wide list of currently active QScroller objects.
static Qt::GestureType grabGesture(QObject *target, ScrollerGestureType gestureType=TouchGesture)
Registers a custom scroll gesture recognizer, grabs it for the target and returns the resulting gestu...
bool handleInput(Input input, const QPointF &position, qint64 timestamp=0)
This function is used by gesture recognizers to inform the scroller about a new input event.
void resendPrepareEvent()
This function resends the QScrollPrepareEvent.
void scrollTo(const QPointF &pos)
Starts scrolling the widget so that point pos is at the top-left position in the viewport.
QScrollerProperties scrollerProperties
The scroller properties of this scroller.
Definition qscroller.h:30
State state
the state of the scroller
Definition qscroller.h:28
void stop()
Stops the scroller and resets its state back to Inactive.
QObject * target() const
Returns the target object of this scroller.
void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin)
Starts scrolling so that the rectangle rect is visible inside the viewport with additional margins sp...
void scrollerPropertiesChanged(const QScrollerProperties &)
QScroller emits this signal whenever its scroller properties change.
State
This enum contains the different QScroller states.
Definition qscroller.h:34
void setScrollerProperties(const QScrollerProperties &prop)
ScrollerGestureType
This enum contains the different gesture types that are supported by the QScroller gesture recognizer...
Definition qscroller.h:43
@ LeftMouseButtonGesture
Definition qscroller.h:45
@ MiddleMouseButtonGesture
Definition qscroller.h:47
@ RightMouseButtonGesture
Definition qscroller.h:46
@ TouchGesture
Definition qscroller.h:44
\inmodule QtCore
Definition qsize.h:208
constexpr qreal width() const noexcept
Returns the width.
Definition qsize.h:332
constexpr qreal height() const noexcept
Returns the height.
Definition qsize.h:335
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
The QWidget class is the base class of all user interface objects.
Definition qwidget.h:99
void setAttribute(Qt::WidgetAttribute, bool on=true)
Sets the attribute attribute on this widget if on is true; otherwise clears the attribute.
void grabGesture(Qt::GestureType type, Qt::GestureFlags flags=Qt::GestureFlags())
Subscribes the widget to a given gesture with specific flags.
void ungrabGesture(Qt::GestureType type)
Unsubscribes the widget from a given gesture type.
QScreen * screen() const
Returns the screen the widget is on.
Definition qwidget.cpp:2496
#define this
Definition dialogs.cpp:9
QOpenGLWidget * widget
[1]
QPixmap p2
QPixmap p1
[0]
QPushButton * button
[2]
rect
[4]
else opt state
[0]
Combined button and popup list for selecting options.
Definition qcompare.h:63
MouseButton
Definition qnamespace.h:56
@ LeftButton
Definition qnamespace.h:58
@ RightButton
Definition qnamespace.h:59
@ MiddleButton
Definition qnamespace.h:60
@ NoButton
Definition qnamespace.h:57
@ WA_AcceptTouchEvents
Definition qnamespace.h:404
Orientation
Definition qnamespace.h:98
@ Horizontal
Definition qnamespace.h:99
@ Vertical
Definition qnamespace.h:100
GestureType
#define Q_UNLIKELY(x)
static const QCssKnownValue positions[NumKnownPositionModes - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qIsNaN(qfloat16 f) noexcept
Definition qfloat16.h:284
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
bool qt_sendSpontaneousEvent(QObject *, QEvent *)
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
int qFloor(T v)
Definition qmath.h:42
int qCeil(T v)
Definition qmath.h:36
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
#define Q_INFINITY
Definition qnumeric.h:77
#define Q_QNAN
Definition qnumeric.h:81
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLsizei const GLfloat * v
[13]
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLboolean r
[2]
GLdouble GLdouble right
GLfloat GLfloat f
GLint left
GLenum type
GLenum target
GLuint start
GLint first
GLint y
struct _cl_event * event
GLdouble s
[6]
Definition qopenglext.h:235
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLuint segments
GLfloat GLfloat p
[1]
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
GLenum GLenum GLenum input
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QPointF clampToRect(const QPointF &p, const QRectF &rect)
Definition qscroller.cpp:95
bool operator<=(const QPointF &p, qreal f)
Definition qscroller.cpp:54
int qSign(qreal r)
bool operator>=(const QPointF &p, qreal f)
Definition qscroller.cpp:66
bool operator>(const QPointF &p, qreal f)
Definition qscroller.cpp:72
QMap< QObject *, QScroller * > ScrollerHash
static qreal progressForValue(const QEasingCurve &curve, qreal value)
QPointF operator/(const QPointF &p1, const QPointF &p2)
Definition qscroller.cpp:90
bool operator<(const QPointF &p, qreal f)
Definition qscroller.cpp:60
QPointF qAbs(const QPointF &p)
Definition qscroller.cpp:78
QT_BEGIN_NAMESPACE bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event)
QPointF operator*(const QPointF &p1, const QPointF &p2)
Definition qscroller.cpp:84
static qreal differentialForProgress(const QEasingCurve &curve, qreal pos)
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
#define sp
QScreen * screen
[1]
Definition main.cpp:29
#define QT_CONFIG(feature)
#define tr(X)
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
if(qFloatDistance(a, b)<(1<< 7))
[0]
QDataStream & operator<<(QDataStream &out, const MyClass &myObj)
[4]
QObject::connect nullptr
QPropertyAnimation animation
[0]
QString dir
[11]
QGraphicsScene scene
[0]
QSizePolicy policy
QScroller * scroller