1// Copyright (C) 2020 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 "qquickflickable_p.h"
5#include "qquickflickable_p_p.h"
6#include "qquickflickablebehavior_p.h"
7#include "qquickwindow.h"
8#include "qquickwindow_p.h"
9#include "qquickmousearea_p.h"
10#if QT_CONFIG(quick_draganddrop)
11#include "qquickdrag_p.h"
12#endif
13
14#include <QtQuick/private/qquickpointerhandler_p.h>
15#include <QtQuick/private/qquicktransition_p.h>
16#include <private/qqmlglobal_p.h>
17
18#include <QtQml/qqmlinfo.h>
19#include <QtGui/qevent.h>
20#include <QtGui/qguiapplication.h>
21#include <QtGui/private/qguiapplication_p.h>
22#include <QtGui/private/qeventpoint_p.h>
23#include <QtGui/qstylehints.h>
24#include <QtCore/qmath.h>
25#include <qpa/qplatformintegration.h>
26
27#include <math.h>
28#include <cmath>
29
30QT_BEGIN_NAMESPACE
31
32Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent)
33Q_LOGGING_CATEGORY(lcFlickable, "qt.quick.flickable")
34Q_LOGGING_CATEGORY(lcFilter, "qt.quick.flickable.filter")
35Q_LOGGING_CATEGORY(lcReplay, "qt.quick.flickable.replay")
36Q_LOGGING_CATEGORY(lcWheel, "qt.quick.flickable.wheel")
37Q_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity")
38
39// RetainGrabVelocity is the maxmimum instantaneous velocity that
40// will ensure the Flickable retains the grab on consecutive flicks.
41static const int RetainGrabVelocity = 100;
42
43// Currently std::round can't be used on Android when using ndk g++, so
44// use C version instead. We could just define two versions of Round, one
45// for float and one for double, but then only one of them would be used
46// and compiler would trigger a warning about unused function.
47//
48// See https://code.google.com/p/android/issues/detail?id=54418
49template<typename T>
50static T Round(T t) {
51 return round(t);
52}
53template<>
54Q_DECL_UNUSED float Round<float>(float f) {
55 return roundf(x: f);
56}
57
58static qreal EaseOvershoot(qreal t) {
59 return qAtan(v: t);
60}
61
62QQuickFlickableVisibleArea::QQuickFlickableVisibleArea(QQuickFlickable *parent)
63 : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.)
64 , m_yPosition(0.), m_heightRatio(0.)
65{
66}
67
68qreal QQuickFlickableVisibleArea::widthRatio() const
69{
70 return m_widthRatio;
71}
72
73qreal QQuickFlickableVisibleArea::xPosition() const
74{
75 return m_xPosition;
76}
77
78qreal QQuickFlickableVisibleArea::heightRatio() const
79{
80 return m_heightRatio;
81}
82
83qreal QQuickFlickableVisibleArea::yPosition() const
84{
85 return m_yPosition;
86}
87
88void QQuickFlickableVisibleArea::updateVisible()
89{
90 QQuickFlickablePrivate *p = QQuickFlickablePrivate::get(o: flickable);
91
92 bool changeX = false;
93 bool changeY = false;
94 bool changeWidth = false;
95 bool changeHeight = false;
96
97 // Vertical
98 const qreal viewheight = flickable->height();
99 const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent();
100 const qreal maxYBounds = maxyextent + viewheight;
101 qreal pagePos = 0;
102 qreal pageSize = 0;
103 if (!qFuzzyIsNull(d: maxYBounds)) {
104 qreal y = p->pixelAligned ? Round(t: p->vData.move.value()) : p->vData.move.value();
105 pagePos = (-y + flickable->minYExtent()) / maxYBounds;
106 pageSize = viewheight / maxYBounds;
107 }
108
109 if (pageSize != m_heightRatio) {
110 m_heightRatio = pageSize;
111 changeHeight = true;
112 }
113 if (pagePos != m_yPosition) {
114 m_yPosition = pagePos;
115 changeY = true;
116 }
117
118 // Horizontal
119 const qreal viewwidth = flickable->width();
120 const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent();
121 const qreal maxXBounds = maxxextent + viewwidth;
122 if (!qFuzzyIsNull(d: maxXBounds)) {
123 qreal x = p->pixelAligned ? Round(t: p->hData.move.value()) : p->hData.move.value();
124 pagePos = (-x + flickable->minXExtent()) / maxXBounds;
125 pageSize = viewwidth / maxXBounds;
126 } else {
127 pagePos = 0;
128 pageSize = 0;
129 }
130
131 if (pageSize != m_widthRatio) {
132 m_widthRatio = pageSize;
133 changeWidth = true;
134 }
135 if (pagePos != m_xPosition) {
136 m_xPosition = pagePos;
137 changeX = true;
138 }
139
140 if (changeX)
141 emit xPositionChanged(xPosition: m_xPosition);
142 if (changeY)
143 emit yPositionChanged(yPosition: m_yPosition);
144 if (changeWidth)
145 emit widthRatioChanged(widthRatio: m_widthRatio);
146 if (changeHeight)
147 emit heightRatioChanged(heightRatio: m_heightRatio);
148}
149
150
151class QQuickFlickableReboundTransition : public QQuickTransitionManager
152{
153public:
154 QQuickFlickableReboundTransition(QQuickFlickable *f, const QString &name)
155 : flickable(f), axisData(nullptr), propName(name), active(false)
156 {
157 }
158
159 ~QQuickFlickableReboundTransition()
160 {
161 flickable = nullptr;
162 }
163
164 bool startTransition(QQuickFlickablePrivate::AxisData *data, qreal toPos) {
165 QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(o: flickable);
166 if (!fp->rebound || !fp->rebound->enabled())
167 return false;
168 active = true;
169 axisData = data;
170 axisData->transitionTo = toPos;
171 axisData->transitionToSet = true;
172
173 actions.clear();
174 actions << QQuickStateAction(fp->contentItem, propName, toPos);
175 QQuickTransitionManager::transition(actions, transition: fp->rebound, defaultTarget: fp->contentItem);
176 return true;
177 }
178
179 bool isActive() const {
180 return active;
181 }
182
183 void stopTransition() {
184 if (!flickable || !isRunning())
185 return;
186 QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(o: flickable);
187 if (axisData == &fp->hData)
188 axisData->move.setValue(-flickable->contentX());
189 else
190 axisData->move.setValue(-flickable->contentY());
191 active = false;
192 cancel();
193 }
194
195protected:
196 void finished() override {
197 if (!flickable)
198 return;
199 axisData->move.setValue(axisData->transitionTo);
200 QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(o: flickable);
201 active = false;
202
203 if (!fp->hData.transitionToBounds->isActive()
204 && !fp->vData.transitionToBounds->isActive()) {
205 flickable->movementEnding();
206 }
207 }
208
209private:
210 QQuickStateOperation::ActionList actions;
211 QQuickFlickable *flickable;
212 QQuickFlickablePrivate::AxisData *axisData;
213 QString propName;
214 bool active;
215};
216
217QQuickFlickablePrivate::AxisData::~AxisData()
218{
219 delete transitionToBounds;
220}
221
222class QQuickFlickableContentItem : public QQuickItem
223{
224 /*!
225 \internal
226 The flickable area inside the viewport can be bigger than the bounds of the
227 content item itself, if the flickable is using non-zero extents (as returned
228 by e.g minXExtent()). Since the default implementation in QQuickItem::contains()
229 only checks if the point is inside the bounds of the item, we need to override it
230 to check the extents as well. The easist way to do this is to simply check if the
231 point is inside the bounds of the flickable rather than the content item.
232 */
233 bool contains(const QPointF &point) const override
234 {
235 const QQuickItem *flickable = parentItem();
236 const QPointF posInFlickable = flickable->mapFromItem(item: this, point);
237 return flickable->contains(point: posInFlickable);
238 }
239};
240
241QQuickFlickablePrivate::QQuickFlickablePrivate()
242 : contentItem(new QQuickFlickableContentItem)
243 , hData(this, &QQuickFlickablePrivate::setViewportX)
244 , vData(this, &QQuickFlickablePrivate::setViewportY)
245 , hMoved(false), vMoved(false)
246 , stealMouse(false), pressed(false)
247 , scrollingPhase(false), interactive(true), calcVelocity(false)
248 , pixelAligned(false)
249 , syncDrag(false)
250 , lastPosTime(-1)
251 , lastPressTime(0)
252 , deceleration(QGuiApplicationPrivate::platformIntegration()->styleHint(hint: QPlatformIntegration::FlickDeceleration).toReal())
253 , wheelDeceleration(15000)
254 , maxVelocity(QGuiApplicationPrivate::platformIntegration()->styleHint(hint: QPlatformIntegration::FlickMaximumVelocity).toReal())
255 , delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400)
256 , flickBoost(1.0), initialWheelFlickDistance(qApp->styleHints()->wheelScrollLines() * 24)
257 , fixupMode(Normal), vTime(0), visibleArea(nullptr)
258 , flickableDirection(QQuickFlickable::AutoFlickDirection)
259 , boundsBehavior(QQuickFlickable::DragAndOvershootBounds)
260 , boundsMovement(QQuickFlickable::FollowBoundsBehavior)
261 , rebound(nullptr)
262{
263 const int wheelDecelerationEnv = qEnvironmentVariableIntValue(varName: "QT_QUICK_FLICKABLE_WHEEL_DECELERATION");
264 if (wheelDecelerationEnv > 0)
265 wheelDeceleration = wheelDecelerationEnv;
266}
267
268void QQuickFlickablePrivate::init()
269{
270 Q_Q(QQuickFlickable);
271 QQml_setParent_noEvent(object: contentItem, parent: q);
272 contentItem->setParentItem(q);
273 qmlobject_connect(&timeline, QQuickTimeLine, SIGNAL(completed()),
274 q, QQuickFlickable, SLOT(timelineCompleted()));
275 qmlobject_connect(&velocityTimeline, QQuickTimeLine, SIGNAL(completed()),
276 q, QQuickFlickable, SLOT(velocityTimelineCompleted()));
277 q->setAcceptedMouseButtons(Qt::LeftButton);
278 q->setAcceptTouchEvents(true);
279 q->setFiltersChildMouseEvents(true);
280 q->setFlag(flag: QQuickItem::ItemIsViewport);
281 QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(item: contentItem);
282 viewportPrivate->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry);
283}
284
285/*!
286 \internal
287 Returns the distance to overshoot, given \a velocity.
288 Will be in range 0 - velocity / 3, but limited to a max of QML_FLICK_OVERSHOOT
289*/
290qreal QQuickFlickablePrivate::overShootDistance(qreal velocity) const
291{
292 if (maxVelocity <= 0)
293 return 0;
294
295 return qMin(a: qreal(QML_FLICK_OVERSHOOT), b: velocity / 3);
296}
297
298void QQuickFlickablePrivate::AxisData::addVelocitySample(qreal v, qreal maxVelocity)
299{
300 if (v > maxVelocity)
301 v = maxVelocity;
302 else if (v < -maxVelocity)
303 v = -maxVelocity;
304 velocityBuffer.append(v);
305 if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
306 velocityBuffer.remove(idx: 0);
307}
308
309void QQuickFlickablePrivate::AxisData::updateVelocity()
310{
311 velocity = 0;
312 if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
313 int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
314 for (int i = 0; i < count; ++i) {
315 qreal v = velocityBuffer.at(idx: i);
316 velocity += v;
317 }
318 velocity /= count;
319 }
320}
321
322void QQuickFlickablePrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &oldGeom)
323{
324 Q_Q(QQuickFlickable);
325 if (item == contentItem) {
326 Qt::Orientations orient;
327 if (change.xChange())
328 orient |= Qt::Horizontal;
329 if (change.yChange())
330 orient |= Qt::Vertical;
331 if (orient) {
332 q->viewportMoved(orient);
333 const QPointF deltaMoved = item->position() - oldGeom.topLeft();
334 if (hData.contentPositionChangedExternallyDuringDrag)
335 hData.pressPos += deltaMoved.x();
336 if (vData.contentPositionChangedExternallyDuringDrag)
337 vData.pressPos += deltaMoved.y();
338 }
339 if (orient & Qt::Horizontal)
340 emit q->contentXChanged();
341 if (orient & Qt::Vertical)
342 emit q->contentYChanged();
343 }
344}
345
346bool QQuickFlickablePrivate::flickX(QEvent::Type eventType, qreal velocity)
347{
348 Q_Q(QQuickFlickable);
349 return flick(data&: hData, minExtent: q->minXExtent(), maxExtent: q->maxXExtent(), vSize: q->width(), fixupCallback: fixupX_callback, eventType, velocity);
350}
351
352bool QQuickFlickablePrivate::flickY(QEvent::Type eventType, qreal velocity)
353{
354 Q_Q(QQuickFlickable);
355 return flick(data&: vData, minExtent: q->minYExtent(), maxExtent: q->maxYExtent(), vSize: q->height(), fixupCallback: fixupY_callback, eventType, velocity);
356}
357
358bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal,
359 QQuickTimeLineCallback::Callback fixupCallback,
360 QEvent::Type eventType, qreal velocity)
361{
362 Q_Q(QQuickFlickable);
363 qreal maxDistance = -1;
364 data.fixingUp = false;
365 // -ve velocity means list is moving up
366 if (velocity > 0) {
367 maxDistance = qAbs(t: minExtent - data.move.value());
368 data.flickTarget = minExtent;
369 } else {
370 maxDistance = qAbs(t: maxExtent - data.move.value());
371 data.flickTarget = maxExtent;
372 }
373 if (maxDistance > 0 || boundsBehavior & QQuickFlickable::OvershootBounds) {
374 qreal v = velocity;
375 if (maxVelocity != -1 && maxVelocity < qAbs(t: v)) {
376 if (v < 0)
377 v = -maxVelocity;
378 else
379 v = maxVelocity;
380 }
381
382 qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
383 qCDebug(lcFlickable) << "choosing deceleration" << accel << "for" << eventType;
384 // adjust accel so that we hit a full pixel
385 qreal v2 = v * v;
386 qreal dist = v2 / (accel * 2.0);
387 if (v > 0)
388 dist = -dist;
389 qreal target = -Round(t: -(data.move.value() - dist));
390 dist = -target + data.move.value();
391 accel = v2 / (2.0f * qAbs(t: dist));
392
393 resetTimeline(data);
394 if (!data.inOvershoot) {
395 if (boundsBehavior & QQuickFlickable::OvershootBounds)
396 timeline.accel(data.move, velocity: v, accel);
397 else
398 timeline.accel(data.move, velocity: v, accel, maxDistance);
399 }
400 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
401
402 if (&data == &hData)
403 return !hData.flicking && q->xflick();
404 else if (&data == &vData)
405 return !vData.flicking && q->yflick();
406 return false;
407 } else {
408 resetTimeline(data);
409 fixup(data, minExtent, maxExtent);
410 return false;
411 }
412}
413
414void QQuickFlickablePrivate::fixupY_callback(void *data)
415{
416 ((QQuickFlickablePrivate *)data)->fixupY();
417}
418
419void QQuickFlickablePrivate::fixupX_callback(void *data)
420{
421 ((QQuickFlickablePrivate *)data)->fixupX();
422}
423
424void QQuickFlickablePrivate::fixupX()
425{
426 Q_Q(QQuickFlickable);
427 if (!q->isComponentComplete())
428 return; //Do not fixup from initialization values
429 fixup(data&: hData, minExtent: q->minXExtent(), maxExtent: q->maxXExtent());
430}
431
432void QQuickFlickablePrivate::fixupY()
433{
434 Q_Q(QQuickFlickable);
435 if (!q->isComponentComplete())
436 return; //Do not fixup from initialization values
437 fixup(data&: vData, minExtent: q->minYExtent(), maxExtent: q->maxYExtent());
438}
439
440/*!
441 \internal
442
443 Adjusts the contentItem's position via the timeline.
444 This function is used by QQuickFlickablePrivate::fixup in order to
445 position the contentItem back into the viewport, in case flicking,
446 dragging or geometry adjustments moved it outside of bounds.
447*/
448void QQuickFlickablePrivate::adjustContentPos(AxisData &data, qreal toPos)
449{
450 Q_Q(QQuickFlickable);
451 switch (fixupMode) {
452 case Immediate:
453 timeline.set(data.move, toPos);
454 break;
455 case ExtentChanged:
456 // The target has changed. Don't start from the beginning; just complete the
457 // second half of the animation using the new extent.
458 timeline.move(data.move, destination: toPos, QEasingCurve(QEasingCurve::OutExpo), time: 3*fixupDuration/4);
459 data.fixingUp = true;
460 break;
461 default: {
462 if (data.transitionToBounds && data.transitionToBounds->startTransition(data: &data, toPos)) {
463 q->movementStarting();
464 data.fixingUp = true;
465 } else {
466 qreal dist = toPos - data.move;
467 timeline.move(data.move, destination: toPos - dist/2, QEasingCurve(QEasingCurve::InQuad), time: fixupDuration/4);
468 timeline.move(data.move, destination: toPos, QEasingCurve(QEasingCurve::OutExpo), time: 3*fixupDuration/4);
469 data.fixingUp = true;
470 }
471 }
472 }
473}
474
475void QQuickFlickablePrivate::resetTimeline(AxisData &data)
476{
477 timeline.reset(data.move);
478 if (data.transitionToBounds)
479 data.transitionToBounds->stopTransition();
480}
481
482void QQuickFlickablePrivate::clearTimeline()
483{
484 timeline.clear();
485 if (hData.transitionToBounds)
486 hData.transitionToBounds->stopTransition();
487 if (vData.transitionToBounds)
488 vData.transitionToBounds->stopTransition();
489}
490
491/*!
492 \internal
493
494 This function should be called after the contentItem has been moved, either programmatically,
495 or by the timeline (as a result of a flick).
496 It ensures that the contentItem will be moved back into bounds,
497 in case it was flicked outside of the visible area.
498
499 The positional adjustment will usually be animated by the timeline, unless the fixupMode is set to Immediate.
500*/
501void QQuickFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent)
502{
503 if (data.move.value() >= minExtent || maxExtent > minExtent) {
504 resetTimeline(data);
505 if (data.move.value() != minExtent) {
506 adjustContentPos(data, toPos: minExtent);
507 }
508 } else if (data.move.value() <= maxExtent) {
509 resetTimeline(data);
510 adjustContentPos(data, toPos: maxExtent);
511 } else if (-Round(t: -data.move.value()) != data.move.value()) {
512 // We could animate, but since it is less than 0.5 pixel it's probably not worthwhile.
513 resetTimeline(data);
514 qreal val = data.move.value();
515 if (std::abs(x: -Round(t: -val) - val) < 0.25) // round small differences
516 val = -Round(t: -val);
517 else if (data.smoothVelocity.value() > 0) // continue direction of motion for larger
518 val = -std::floor(x: -val);
519 else if (data.smoothVelocity.value() < 0)
520 val = -std::ceil(x: -val);
521 else // otherwise round
522 val = -Round(t: -val);
523 timeline.set(data.move, val);
524 }
525 data.inOvershoot = false;
526 fixupMode = Normal;
527 data.vTime = timeline.time();
528}
529
530static bool fuzzyLessThanOrEqualTo(qreal a, qreal b)
531{
532 if (a == 0.0 || b == 0.0) {
533 // qFuzzyCompare is broken
534 a += 1.0;
535 b += 1.0;
536 }
537 return a <= b || qFuzzyCompare(p1: a, p2: b);
538}
539
540/*!
541 \internal
542
543 This function's main purpose is to update the atBeginning and atEnd flags
544 in hData and vData. It should be called when the contentItem has moved,
545 to ensure that hData and vData are up to date.
546
547 The origin will also be updated, if AxisData::markExtentsDirty has been called
548*/
549void QQuickFlickablePrivate::updateBeginningEnd()
550{
551 Q_Q(QQuickFlickable);
552 bool atXBeginningChange = false, atXEndChange = false;
553 bool atYBeginningChange = false, atYEndChange = false;
554
555 // Vertical
556 const qreal maxyextent = -q->maxYExtent();
557 const qreal minyextent = -q->minYExtent();
558 const qreal ypos = pixelAligned ? -std::round(x: vData.move.value()) : -vData.move.value();
559 bool atBeginning = fuzzyLessThanOrEqualTo(a: ypos, b: std::ceil(x: minyextent));
560 bool atEnd = fuzzyLessThanOrEqualTo(a: std::floor(x: maxyextent), b: ypos);
561
562 if (atBeginning != vData.atBeginning) {
563 vData.atBeginning = atBeginning;
564 atYBeginningChange = true;
565 if (!vData.moving && atBeginning)
566 vData.smoothVelocity.setValue(0);
567 }
568 if (atEnd != vData.atEnd) {
569 vData.atEnd = atEnd;
570 atYEndChange = true;
571 if (!vData.moving && atEnd)
572 vData.smoothVelocity.setValue(0);
573 }
574
575 // Horizontal
576 const qreal maxxextent = -q->maxXExtent();
577 const qreal minxextent = -q->minXExtent();
578 const qreal xpos = pixelAligned ? -std::round(x: hData.move.value()) : -hData.move.value();
579 atBeginning = fuzzyLessThanOrEqualTo(a: xpos, b: std::ceil(x: minxextent));
580 atEnd = fuzzyLessThanOrEqualTo(a: std::floor(x: maxxextent), b: xpos);
581
582 if (atBeginning != hData.atBeginning) {
583 hData.atBeginning = atBeginning;
584 atXBeginningChange = true;
585 if (!hData.moving && atBeginning)
586 hData.smoothVelocity.setValue(0);
587 }
588 if (atEnd != hData.atEnd) {
589 hData.atEnd = atEnd;
590 atXEndChange = true;
591 if (!hData.moving && atEnd)
592 hData.smoothVelocity.setValue(0);
593 }
594
595 if (vData.extentsChanged) {
596 vData.extentsChanged = false;
597 qreal originY = q->originY();
598 if (vData.origin != originY) {
599 vData.origin = originY;
600 emit q->originYChanged();
601 }
602 }
603
604 if (hData.extentsChanged) {
605 hData.extentsChanged = false;
606 qreal originX = q->originX();
607 if (hData.origin != originX) {
608 hData.origin = originX;
609 emit q->originXChanged();
610 }
611 }
612
613 if (atXEndChange || atYEndChange || atXBeginningChange || atYBeginningChange)
614 emit q->isAtBoundaryChanged();
615 if (atXEndChange)
616 emit q->atXEndChanged();
617 if (atXBeginningChange)
618 emit q->atXBeginningChanged();
619 if (atYEndChange)
620 emit q->atYEndChanged();
621 if (atYBeginningChange)
622 emit q->atYBeginningChanged();
623
624 if (visibleArea)
625 visibleArea->updateVisible();
626}
627
628/*!
629 \qmlsignal QtQuick::Flickable::dragStarted()
630
631 This signal is emitted when the view starts to be dragged due to user
632 interaction.
633*/
634
635/*!
636 \qmlsignal QtQuick::Flickable::dragEnded()
637
638 This signal is emitted when the user stops dragging the view.
639
640 If the velocity of the drag is sufficient at the time the
641 touch/mouse button is released then a flick will start.
642*/
643
644/*!
645 \qmltype Flickable
646 \instantiates QQuickFlickable
647 \inqmlmodule QtQuick
648 \ingroup qtquick-input
649 \ingroup qtquick-containers
650
651 \brief Provides a surface that can be "flicked".
652 \inherits Item
653
654 The Flickable item places its children on a surface that can be dragged
655 and flicked, causing the view onto the child items to scroll. This
656 behavior forms the basis of Items that are designed to show large numbers
657 of child items, such as \l ListView and \l GridView.
658
659 In traditional user interfaces, views can be scrolled using standard
660 controls, such as scroll bars and arrow buttons. In some situations, it
661 is also possible to drag the view directly by pressing and holding a
662 mouse button while moving the cursor. In touch-based user interfaces,
663 this dragging action is often complemented with a flicking action, where
664 scrolling continues after the user has stopped touching the view.
665
666 Flickable does not automatically clip its contents. If it is not used as
667 a full-screen item, you should consider setting the \l{Item::}{clip} property
668 to true.
669
670 \section1 Example Usage
671
672 \div {class="float-right"}
673 \inlineimage flickable.gif
674 \enddiv
675
676 The following example shows a small view onto a large image in which the
677 user can drag or flick the image in order to view different parts of it.
678
679 \snippet qml/flickable.qml document
680
681 \clearfloat
682
683 Items declared as children of a Flickable are automatically parented to the
684 Flickable's \l contentItem. This should be taken into account when
685 operating on the children of the Flickable; it is usually the children of
686 \c contentItem that are relevant. For example, the bound of Items added
687 to the Flickable will be available by \c contentItem.childrenRect
688
689 \section1 Examples of contentX and contentY
690
691 The following images demonstrate a flickable being flicked in various
692 directions and the resulting \l contentX and \l contentY values.
693 The blue square represents the flickable's content, and the black
694 border represents the bounds of the flickable.
695
696 \table
697 \row
698 \li \image flickable-contentXY-resting.png
699 \li The \c contentX and \c contentY are both \c 0.
700 \row
701 \li \image flickable-contentXY-top-left.png
702 \li The \c contentX and the \c contentY are both \c 50.
703 \row
704 \li \image flickable-contentXY-top-right.png
705 \li The \c contentX is \c -50 and the \c contentY is \c 50.
706 \row
707 \li \image flickable-contentXY-bottom-right.png
708 \li The \c contentX and the \c contentY are both \c -50.
709 \row
710 \li \image flickable-contentXY-bottom-left.png
711 \li The \c contentX is \c 50 and the \c contentY is \c -50.
712 \endtable
713
714 \section1 Limitations
715
716 \note Due to an implementation detail, items placed inside a Flickable
717 cannot anchor to the Flickable. Instead, use \l {Item::}{parent}, which
718 refers to the Flickable's \l contentItem. The size of the content item is
719 determined by \l contentWidth and \l contentHeight.
720*/
721
722/*!
723 \qmlsignal QtQuick::Flickable::movementStarted()
724
725 This signal is emitted when the view begins moving due to user
726 interaction or a generated flick().
727*/
728
729/*!
730 \qmlsignal QtQuick::Flickable::movementEnded()
731
732 This signal is emitted when the view stops moving due to user
733 interaction or a generated flick(). If a flick was active, this signal will
734 be emitted once the flick stops. If a flick was not
735 active, this signal will be emitted when the
736 user stops dragging - i.e. a mouse or touch release.
737*/
738
739/*!
740 \qmlsignal QtQuick::Flickable::flickStarted()
741
742 This signal is emitted when the view is flicked. A flick
743 starts from the point that the mouse or touch is released,
744 while still in motion.
745*/
746
747/*!
748 \qmlsignal QtQuick::Flickable::flickEnded()
749
750 This signal is emitted when the view stops moving after a flick
751 or a series of flicks.
752*/
753
754/*!
755 \qmlpropertygroup QtQuick::Flickable::visibleArea
756 \qmlproperty real QtQuick::Flickable::visibleArea.xPosition
757 \qmlproperty real QtQuick::Flickable::visibleArea.widthRatio
758 \qmlproperty real QtQuick::Flickable::visibleArea.yPosition
759 \qmlproperty real QtQuick::Flickable::visibleArea.heightRatio
760
761 These properties describe the position and size of the currently viewed area.
762 The size is defined as the percentage of the full view currently visible,
763 scaled to 0.0 - 1.0. The page position is usually in the range 0.0 (beginning) to
764 1.0 minus size ratio (end), i.e. \c yPosition is in the range 0.0 to 1.0-\c heightRatio.
765 However, it is possible for the contents to be dragged outside of the normal
766 range, resulting in the page positions also being outside the normal range.
767
768 These properties are typically used to draw a scrollbar. For example:
769
770 \snippet qml/flickableScrollbar.qml 0
771 \dots 8
772 \snippet qml/flickableScrollbar.qml 1
773*/
774QQuickFlickable::QQuickFlickable(QQuickItem *parent)
775 : QQuickItem(*(new QQuickFlickablePrivate), parent)
776{
777 Q_D(QQuickFlickable);
778 d->init();
779}
780
781QQuickFlickable::QQuickFlickable(QQuickFlickablePrivate &dd, QQuickItem *parent)
782 : QQuickItem(dd, parent)
783{
784 Q_D(QQuickFlickable);
785 d->init();
786}
787
788QQuickFlickable::~QQuickFlickable()
789{
790}
791
792/*!
793 \qmlproperty real QtQuick::Flickable::contentX
794 \qmlproperty real QtQuick::Flickable::contentY
795
796 These properties hold the surface coordinate currently at the top-left
797 corner of the Flickable. For example, if you flick an image up 100 pixels,
798 \c contentY will increase by 100.
799
800 \note If you flick back to the origin (the top-left corner), after the
801 rebound animation, \c contentX will settle to the same value as \c originX,
802 and \c contentY to \c originY. These are usually (0,0), however ListView
803 and GridView may have an arbitrary origin due to delegate size variation,
804 or item insertion/removal outside the visible region. So if you want to
805 implement something like a vertical scrollbar, one way is to use
806 \c {y: (contentY - originY) * (height / contentHeight)}
807 for the position; another way is to use the normalized values in
808 \l {QtQuick::Flickable::visibleArea}{visibleArea}.
809
810 \sa {Examples of contentX and contentY}, originX, originY
811*/
812qreal QQuickFlickable::contentX() const
813{
814 Q_D(const QQuickFlickable);
815 return -d->contentItem->x();
816}
817
818void QQuickFlickable::setContentX(qreal pos)
819{
820 Q_D(QQuickFlickable);
821 d->hData.explicitValue = true;
822 d->resetTimeline(data&: d->hData);
823 d->hData.vTime = d->timeline.time();
824 if (isMoving() || isFlicking())
825 movementEnding(hMovementEnding: true, vMovementEnding: false);
826 if (!qFuzzyCompare(p1: -pos, p2: d->hData.move.value())) {
827 d->hData.contentPositionChangedExternallyDuringDrag = d->hData.dragging;
828 d->hData.move.setValue(-pos);
829 d->hData.contentPositionChangedExternallyDuringDrag = false;
830 }
831}
832
833qreal QQuickFlickable::contentY() const
834{
835 Q_D(const QQuickFlickable);
836 return -d->contentItem->y();
837}
838
839void QQuickFlickable::setContentY(qreal pos)
840{
841 Q_D(QQuickFlickable);
842 d->vData.explicitValue = true;
843 d->resetTimeline(data&: d->vData);
844 d->vData.vTime = d->timeline.time();
845 if (isMoving() || isFlicking())
846 movementEnding(hMovementEnding: false, vMovementEnding: true);
847 if (!qFuzzyCompare(p1: -pos, p2: d->vData.move.value())) {
848 d->vData.contentPositionChangedExternallyDuringDrag = d->vData.dragging;
849 d->vData.move.setValue(-pos);
850 d->vData.contentPositionChangedExternallyDuringDrag = false;
851 }
852}
853
854/*!
855 \qmlproperty bool QtQuick::Flickable::interactive
856
857 This property describes whether the user can interact with the Flickable.
858 A user cannot drag or flick a Flickable that is not interactive.
859
860 By default, this property is true.
861
862 This property is useful for temporarily disabling flicking. This allows
863 special interaction with Flickable's children; for example, you might want
864 to freeze a flickable map while scrolling through a pop-up dialog that
865 is a child of the Flickable.
866*/
867bool QQuickFlickable::isInteractive() const
868{
869 Q_D(const QQuickFlickable);
870 return d->interactive;
871}
872
873void QQuickFlickable::setInteractive(bool interactive)
874{
875 Q_D(QQuickFlickable);
876 if (interactive != d->interactive) {
877 d->interactive = interactive;
878 if (!interactive) {
879 d->cancelInteraction();
880 }
881 emit interactiveChanged();
882 }
883}
884
885/*!
886 \qmlproperty real QtQuick::Flickable::horizontalVelocity
887 \qmlproperty real QtQuick::Flickable::verticalVelocity
888
889 The instantaneous velocity of movement along the x and y axes, in pixels/sec.
890
891 The reported velocity is smoothed to avoid erratic output.
892
893 Note that for views with a large content size (more than 10 times the view size),
894 the velocity of the flick may exceed the velocity of the touch in the case
895 of multiple quick consecutive flicks. This allows the user to flick faster
896 through large content.
897*/
898qreal QQuickFlickable::horizontalVelocity() const
899{
900 Q_D(const QQuickFlickable);
901 return d->hData.smoothVelocity.value();
902}
903
904qreal QQuickFlickable::verticalVelocity() const
905{
906 Q_D(const QQuickFlickable);
907 return d->vData.smoothVelocity.value();
908}
909
910/*!
911 \qmlproperty bool QtQuick::Flickable::atXBeginning
912 \qmlproperty bool QtQuick::Flickable::atXEnd
913 \qmlproperty bool QtQuick::Flickable::atYBeginning
914 \qmlproperty bool QtQuick::Flickable::atYEnd
915
916 These properties are true if the flickable view is positioned at the beginning,
917 or end respectively.
918*/
919bool QQuickFlickable::isAtXEnd() const
920{
921 Q_D(const QQuickFlickable);
922 return d->hData.atEnd;
923}
924
925bool QQuickFlickable::isAtXBeginning() const
926{
927 Q_D(const QQuickFlickable);
928 return d->hData.atBeginning;
929}
930
931bool QQuickFlickable::isAtYEnd() const
932{
933 Q_D(const QQuickFlickable);
934 return d->vData.atEnd;
935}
936
937bool QQuickFlickable::isAtYBeginning() const
938{
939 Q_D(const QQuickFlickable);
940 return d->vData.atBeginning;
941}
942
943/*!
944 \qmlproperty Item QtQuick::Flickable::contentItem
945
946 The internal item that contains the Items to be moved in the Flickable.
947
948 Items declared as children of a Flickable are automatically parented to the Flickable's contentItem.
949
950 Items created dynamically need to be explicitly parented to the \e contentItem:
951 \code
952 Flickable {
953 id: myFlickable
954 function addItem(file) {
955 var component = Qt.createComponent(file)
956 component.createObject(myFlickable.contentItem);
957 }
958 }
959 \endcode
960*/
961QQuickItem *QQuickFlickable::contentItem() const
962{
963 Q_D(const QQuickFlickable);
964 return d->contentItem;
965}
966
967QQuickFlickableVisibleArea *QQuickFlickable::visibleArea()
968{
969 Q_D(QQuickFlickable);
970 if (!d->visibleArea) {
971 d->visibleArea = new QQuickFlickableVisibleArea(this);
972 d->visibleArea->updateVisible(); // calculate initial ratios
973 }
974 return d->visibleArea;
975}
976
977/*!
978 \qmlproperty enumeration QtQuick::Flickable::flickableDirection
979
980 This property determines which directions the view can be flicked.
981
982 \list
983 \li Flickable.AutoFlickDirection (default) - allows flicking vertically if the
984 \e contentHeight is not equal to the \e height of the Flickable.
985 Allows flicking horizontally if the \e contentWidth is not equal
986 to the \e width of the Flickable.
987 \li Flickable.AutoFlickIfNeeded - allows flicking vertically if the
988 \e contentHeight is greater than the \e height of the Flickable.
989 Allows flicking horizontally if the \e contentWidth is greater than
990 to the \e width of the Flickable. (since \c{QtQuick 2.7})
991 \li Flickable.HorizontalFlick - allows flicking horizontally.
992 \li Flickable.VerticalFlick - allows flicking vertically.
993 \li Flickable.HorizontalAndVerticalFlick - allows flicking in both directions.
994 \endlist
995*/
996QQuickFlickable::FlickableDirection QQuickFlickable::flickableDirection() const
997{
998 Q_D(const QQuickFlickable);
999 return d->flickableDirection;
1000}
1001
1002void QQuickFlickable::setFlickableDirection(FlickableDirection direction)
1003{
1004 Q_D(QQuickFlickable);
1005 if (direction != d->flickableDirection) {
1006 d->flickableDirection = direction;
1007 emit flickableDirectionChanged();
1008 }
1009}
1010
1011/*!
1012 \qmlproperty bool QtQuick::Flickable::pixelAligned
1013
1014 This property sets the alignment of \l contentX and \l contentY to
1015 pixels (\c true) or subpixels (\c false).
1016
1017 Enable pixelAligned to optimize for still content or moving content with
1018 high constrast edges, such as one-pixel-wide lines, text or vector graphics.
1019 Disable pixelAligned when optimizing for animation quality.
1020
1021 The default is \c false.
1022*/
1023bool QQuickFlickable::pixelAligned() const
1024{
1025 Q_D(const QQuickFlickable);
1026 return d->pixelAligned;
1027}
1028
1029void QQuickFlickable::setPixelAligned(bool align)
1030{
1031 Q_D(QQuickFlickable);
1032 if (align != d->pixelAligned) {
1033 d->pixelAligned = align;
1034 emit pixelAlignedChanged();
1035 }
1036}
1037
1038/*!
1039 \qmlproperty bool QtQuick::Flickable::synchronousDrag
1040 \since 5.12
1041
1042 If this property is set to true, then when the mouse or touchpoint moves
1043 far enough to begin dragging the content, the content will jump, such that
1044 the content pixel which was under the cursor or touchpoint when pressed
1045 remains under that point.
1046
1047 The default is \c false, which provides a smoother experience (no jump)
1048 at the cost that some of the drag distance is "lost" at the beginning.
1049*/
1050bool QQuickFlickable::synchronousDrag() const
1051{
1052 Q_D(const QQuickFlickable);
1053 return d->syncDrag;
1054}
1055
1056void QQuickFlickable::setSynchronousDrag(bool v)
1057{
1058 Q_D(QQuickFlickable);
1059 if (v != d->syncDrag) {
1060 d->syncDrag = v;
1061 emit synchronousDragChanged();
1062 }
1063}
1064
1065/*! \internal
1066 Take the velocity of the first point from the given \a event and transform
1067 it to the local coordinate system (taking scale and rotation into account).
1068*/
1069QVector2D QQuickFlickablePrivate::firstPointLocalVelocity(QPointerEvent *event)
1070{
1071 QTransform transform = windowToItemTransform();
1072 // rotate and scale the velocity vector from scene to local
1073 return QVector2D(transform.map(p: event->point(i: 0).velocity().toPointF()) - transform.map(p: QPointF()));
1074}
1075
1076qint64 QQuickFlickablePrivate::computeCurrentTime(QInputEvent *event) const
1077{
1078 if (0 != event->timestamp())
1079 return event->timestamp();
1080 if (!timer.isValid())
1081 return 0LL;
1082 return timer.elapsed();
1083}
1084
1085qreal QQuickFlickablePrivate::devicePixelRatio() const
1086{
1087 return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
1088}
1089
1090void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event)
1091{
1092 Q_Q(QQuickFlickable);
1093 timer.start();
1094 if (interactive && timeline.isActive()
1095 && ((qAbs(t: hData.smoothVelocity.value()) > RetainGrabVelocity && !hData.fixingUp && !hData.inOvershoot)
1096 || (qAbs(t: vData.smoothVelocity.value()) > RetainGrabVelocity && !vData.fixingUp && !vData.inOvershoot))) {
1097 stealMouse = true; // If we've been flicked then steal the click.
1098 int flickTime = timeline.time();
1099 if (flickTime > 600) {
1100 // too long between flicks - cancel boost
1101 hData.continuousFlickVelocity = 0;
1102 vData.continuousFlickVelocity = 0;
1103 flickBoost = 1.0;
1104 } else {
1105 hData.continuousFlickVelocity = -hData.smoothVelocity.value();
1106 vData.continuousFlickVelocity = -vData.smoothVelocity.value();
1107 if (flickTime > 300) // slower flicking - reduce boost
1108 flickBoost = qMax(a: 1.0, b: flickBoost - 0.5);
1109 }
1110 } else {
1111 stealMouse = false;
1112 hData.continuousFlickVelocity = 0;
1113 vData.continuousFlickVelocity = 0;
1114 flickBoost = 1.0;
1115 }
1116 q->setKeepMouseGrab(stealMouse);
1117
1118 maybeBeginDrag(currentTimestamp: computeCurrentTime(event), pressPosn: event->points().first().position());
1119}
1120
1121void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn)
1122{
1123 Q_Q(QQuickFlickable);
1124 clearDelayedPress();
1125 pressed = true;
1126
1127 if (hData.transitionToBounds)
1128 hData.transitionToBounds->stopTransition();
1129 if (vData.transitionToBounds)
1130 vData.transitionToBounds->stopTransition();
1131 if (!hData.fixingUp)
1132 resetTimeline(data&: hData);
1133 if (!vData.fixingUp)
1134 resetTimeline(data&: vData);
1135
1136 hData.reset();
1137 vData.reset();
1138 hData.dragMinBound = q->minXExtent() - hData.startMargin;
1139 vData.dragMinBound = q->minYExtent() - vData.startMargin;
1140 hData.dragMaxBound = q->maxXExtent() + hData.endMargin;
1141 vData.dragMaxBound = q->maxYExtent() + vData.endMargin;
1142 fixupMode = Normal;
1143 lastPos = QPointF();
1144 pressPos = pressPosn;
1145 hData.pressPos = hData.move.value();
1146 vData.pressPos = vData.move.value();
1147 const bool wasFlicking = hData.flicking || vData.flicking;
1148 hData.flickingWhenDragBegan = hData.flicking;
1149 vData.flickingWhenDragBegan = vData.flicking;
1150 if (hData.flicking) {
1151 hData.flicking = false;
1152 emit q->flickingHorizontallyChanged();
1153 }
1154 if (vData.flicking) {
1155 vData.flicking = false;
1156 emit q->flickingVerticallyChanged();
1157 }
1158 if (wasFlicking)
1159 emit q->flickingChanged();
1160 lastPosTime = lastPressTime = currentTimestamp;
1161 vData.velocityTime.start();
1162 hData.velocityTime.start();
1163}
1164
1165void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos,
1166 const QVector2D &deltas, bool overThreshold, bool momentum,
1167 bool velocitySensitiveOverBounds, const QVector2D &velocity)
1168{
1169 Q_Q(QQuickFlickable);
1170 bool rejectY = false;
1171 bool rejectX = false;
1172
1173 bool keepY = q->yflick();
1174 bool keepX = q->xflick();
1175
1176 bool stealY = false;
1177 bool stealX = false;
1178 if (eventType == QEvent::MouseMove) {
1179 stealX = stealY = stealMouse;
1180 } else if (eventType == QEvent::Wheel) {
1181 stealX = stealY = scrollingPhase;
1182 }
1183
1184 bool prevHMoved = hMoved;
1185 bool prevVMoved = vMoved;
1186
1187 qint64 elapsedSincePress = currentTimestamp - lastPressTime;
1188 qCDebug(lcFlickable).nospace() << currentTimestamp << ' ' << eventType << " drag @ " << localPos.x() << ',' << localPos.y()
1189 << " \u0394 " << deltas.x() << ',' << deltas.y() << " vel " << velocity.x() << ',' << velocity.y()
1190 << " thrsld? " << overThreshold << " momentum? " << momentum << " velSens? " << velocitySensitiveOverBounds
1191 << " sincePress " << elapsedSincePress;
1192
1193 if (q->yflick()) {
1194 qreal dy = deltas.y();
1195 if (overThreshold || elapsedSincePress > 200) {
1196 if (!vMoved)
1197 vData.dragStartOffset = dy;
1198 qreal newY = dy + vData.pressPos - (syncDrag ? 0 : vData.dragStartOffset);
1199 // Recalculate bounds in case margins have changed, but use the content
1200 // size estimate taken at the start of the drag in case the drag causes
1201 // the estimate to be altered
1202 const qreal minY = vData.dragMinBound + vData.startMargin;
1203 const qreal maxY = vData.dragMaxBound - vData.endMargin;
1204 if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) {
1205 if (fuzzyLessThanOrEqualTo(a: newY, b: maxY)) {
1206 newY = maxY;
1207 rejectY = vData.pressPos == maxY && vData.move.value() == maxY && dy < 0;
1208 }
1209 if (fuzzyLessThanOrEqualTo(a: minY, b: newY)) {
1210 newY = minY;
1211 rejectY |= vData.pressPos == minY && vData.move.value() == minY && dy > 0;
1212 }
1213 } else {
1214 qreal vel = velocity.y() / QML_FLICK_OVERSHOOTFRICTION;
1215 if (vel > 0. && vel > vData.velocity)
1216 vData.velocity = qMin(a: velocity.y() / QML_FLICK_OVERSHOOTFRICTION, b: maxVelocity);
1217 else if (vel < 0. && vel < vData.velocity)
1218 vData.velocity = qMax(a: velocity.y() / QML_FLICK_OVERSHOOTFRICTION, b: -maxVelocity);
1219 if (newY > minY) {
1220 // Overshoot beyond the top. But don't wait for momentum phase to end before returning to bounds.
1221 if (momentum && vData.atBeginning) {
1222 if (!vData.inRebound) {
1223 vData.inRebound = true;
1224 q->returnToBounds();
1225 }
1226 return;
1227 }
1228 if (velocitySensitiveOverBounds) {
1229 qreal overshoot = (newY - minY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1230 overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio());
1231 newY = minY + overshoot;
1232 } else {
1233 newY = minY + (newY - minY) / 2;
1234 }
1235 } else if (newY < maxY && maxY - minY <= 0) {
1236 // Overshoot beyond the bottom. But don't wait for momentum phase to end before returning to bounds.
1237 if (momentum && vData.atEnd) {
1238 if (!vData.inRebound) {
1239 vData.inRebound = true;
1240 q->returnToBounds();
1241 }
1242 return;
1243 }
1244 if (velocitySensitiveOverBounds) {
1245 qreal overshoot = (newY - maxY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1246 overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio());
1247 newY = maxY - overshoot;
1248 } else {
1249 newY = maxY + (newY - maxY) / 2;
1250 }
1251 }
1252 }
1253 if (!rejectY && stealMouse && dy != 0.0 && dy != vData.previousDragDelta) {
1254 clearTimeline();
1255 vData.move.setValue(newY);
1256 vMoved = true;
1257 }
1258 if (!rejectY && overThreshold)
1259 stealY = true;
1260
1261 if ((newY >= minY && vData.pressPos == minY && vData.move.value() == minY && dy > 0)
1262 || (newY <= maxY && vData.pressPos == maxY && vData.move.value() == maxY && dy < 0)) {
1263 keepY = false;
1264 }
1265 }
1266 vData.previousDragDelta = dy;
1267 }
1268
1269 if (q->xflick()) {
1270 qreal dx = deltas.x();
1271 if (overThreshold || elapsedSincePress > 200) {
1272 if (!hMoved)
1273 hData.dragStartOffset = dx;
1274 qreal newX = dx + hData.pressPos - (syncDrag ? 0 : hData.dragStartOffset);
1275 const qreal minX = hData.dragMinBound + hData.startMargin;
1276 const qreal maxX = hData.dragMaxBound - hData.endMargin;
1277 if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) {
1278 if (fuzzyLessThanOrEqualTo(a: newX, b: maxX)) {
1279 newX = maxX;
1280 rejectX = hData.pressPos == maxX && hData.move.value() == maxX && dx < 0;
1281 }
1282 if (fuzzyLessThanOrEqualTo(a: minX, b: newX)) {
1283 newX = minX;
1284 rejectX |= hData.pressPos == minX && hData.move.value() == minX && dx > 0;
1285 }
1286 } else {
1287 qreal vel = velocity.x() / QML_FLICK_OVERSHOOTFRICTION;
1288 if (vel > 0. && vel > hData.velocity)
1289 hData.velocity = qMin(a: velocity.x() / QML_FLICK_OVERSHOOTFRICTION, b: maxVelocity);
1290 else if (vel < 0. && vel < hData.velocity)
1291 hData.velocity = qMax(a: velocity.x() / QML_FLICK_OVERSHOOTFRICTION, b: -maxVelocity);
1292 if (newX > minX) {
1293 // Overshoot beyond the left. But don't wait for momentum phase to end before returning to bounds.
1294 if (momentum && hData.atBeginning) {
1295 if (!hData.inRebound) {
1296 hData.inRebound = true;
1297 q->returnToBounds();
1298 }
1299 return;
1300 }
1301 if (velocitySensitiveOverBounds) {
1302 qreal overshoot = (newX - minX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1303 overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio());
1304 newX = minX + overshoot;
1305 } else {
1306 newX = minX + (newX - minX) / 2;
1307 }
1308 } else if (newX < maxX && maxX - minX <= 0) {
1309 // Overshoot beyond the right. But don't wait for momentum phase to end before returning to bounds.
1310 if (momentum && hData.atEnd) {
1311 if (!hData.inRebound) {
1312 hData.inRebound = true;
1313 q->returnToBounds();
1314 }
1315 return;
1316 }
1317 if (velocitySensitiveOverBounds) {
1318 qreal overshoot = (newX - maxX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1319 overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(t: overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio());
1320 newX = maxX - overshoot;
1321 } else {
1322 newX = maxX + (newX - maxX) / 2;
1323 }
1324 }
1325 }
1326
1327 if (!rejectX && stealMouse && dx != 0.0 && dx != hData.previousDragDelta) {
1328 clearTimeline();
1329 hData.move.setValue(newX);
1330 hMoved = true;
1331 }
1332
1333 if (!rejectX && overThreshold)
1334 stealX = true;
1335
1336 if ((newX >= minX && vData.pressPos == minX && vData.move.value() == minX && dx > 0)
1337 || (newX <= maxX && vData.pressPos == maxX && vData.move.value() == maxX && dx < 0)) {
1338 keepX = false;
1339 }
1340 }
1341 hData.previousDragDelta = dx;
1342 }
1343
1344 stealMouse = stealX || stealY;
1345 if (stealMouse) {
1346 if ((stealX && keepX) || (stealY && keepY))
1347 q->setKeepMouseGrab(true);
1348 clearDelayedPress();
1349 }
1350
1351 if (rejectY) {
1352 vData.velocityBuffer.clear();
1353 vData.velocity = 0;
1354 }
1355 if (rejectX) {
1356 hData.velocityBuffer.clear();
1357 hData.velocity = 0;
1358 }
1359
1360 if (momentum && !hData.flicking && !vData.flicking)
1361 flickingStarted(flickingH: hData.velocity != 0, flickingV: vData.velocity != 0);
1362 draggingStarting();
1363
1364 if ((hMoved && !prevHMoved) || (vMoved && !prevVMoved))
1365 q->movementStarting();
1366
1367 lastPosTime = currentTimestamp;
1368 if (q->yflick() && !rejectY)
1369 vData.addVelocitySample(v: velocity.y(), maxVelocity);
1370 if (q->xflick() && !rejectX)
1371 hData.addVelocitySample(v: velocity.x(), maxVelocity);
1372 lastPos = localPos;
1373}
1374
1375void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event)
1376{
1377 Q_Q(QQuickFlickable);
1378 if (!interactive || lastPosTime == -1 ||
1379 (event->isSinglePointEvent() && !static_cast<QSinglePointEvent *>(event)->buttons().testFlag(flag: Qt::LeftButton)))
1380 return;
1381
1382 qint64 currentTimestamp = computeCurrentTime(event);
1383 const auto &firstPoint = event->points().first();
1384 const auto &pos = firstPoint.position();
1385 const QVector2D deltas = QVector2D(pos - q->mapFromGlobal(point: firstPoint.globalPressPosition()));
1386 const QVector2D velocity = firstPointLocalVelocity(event);
1387 bool overThreshold = false;
1388
1389 if (event->pointCount() == 1) {
1390 if (q->yflick())
1391 overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(d: deltas.y(), axis: Qt::YAxis, tp: firstPoint);
1392 if (q->xflick())
1393 overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(d: deltas.x(), axis: Qt::XAxis, tp: firstPoint);
1394 } else {
1395 qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event;
1396 }
1397
1398 drag(currentTimestamp, eventType: event->type(), localPos: pos, deltas, overThreshold, momentum: false, velocitySensitiveOverBounds: false, velocity);
1399}
1400
1401void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event)
1402{
1403 Q_Q(QQuickFlickable);
1404 stealMouse = false;
1405 q->setKeepMouseGrab(false);
1406 pressed = false;
1407
1408 // if we drag then pause before release we should not cause a flick.
1409 qint64 elapsed = computeCurrentTime(event) - lastPosTime;
1410
1411 vData.updateVelocity();
1412 hData.updateVelocity();
1413
1414 draggingEnding();
1415
1416 if (lastPosTime == -1)
1417 return;
1418
1419 hData.vTime = vData.vTime = timeline.time();
1420
1421 bool canBoost = false;
1422 const auto pos = event->points().first().position();
1423 const auto pressPos = q->mapFromGlobal(point: event->points().first().globalPressPosition());
1424 const QVector2D eventVelocity = firstPointLocalVelocity(event);
1425 qCDebug(lcVel) << event->deviceType() << event->type() << "velocity" << event->points().first().velocity() << "transformed to local" << eventVelocity;
1426
1427 qreal vVelocity = 0;
1428 if (elapsed < 100 && vData.velocity != 0.) {
1429 vVelocity = (event->device()->capabilities().testFlag(flag: QInputDevice::Capability::Velocity)
1430 ? eventVelocity.y() : vData.velocity);
1431 }
1432 if ((vData.atBeginning && vVelocity > 0.) || (vData.atEnd && vVelocity < 0.)) {
1433 vVelocity /= 2;
1434 } else if (vData.continuousFlickVelocity != 0.0
1435 && vData.viewSize/q->height() > QML_FLICK_MULTIFLICK_RATIO
1436 && ((vVelocity > 0) == (vData.continuousFlickVelocity > 0))
1437 && qAbs(t: vVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) {
1438 // accelerate flick for large view flicked quickly
1439 canBoost = true;
1440 }
1441
1442 qreal hVelocity = 0;
1443 if (elapsed < 100 && hData.velocity != 0.) {
1444 hVelocity = (event->device()->capabilities().testFlag(flag: QInputDevice::Capability::Velocity)
1445 ? eventVelocity.x() : hData.velocity);
1446 }
1447 if ((hData.atBeginning && hVelocity > 0.) || (hData.atEnd && hVelocity < 0.)) {
1448 hVelocity /= 2;
1449 } else if (hData.continuousFlickVelocity != 0.0
1450 && hData.viewSize/q->width() > QML_FLICK_MULTIFLICK_RATIO
1451 && ((hVelocity > 0) == (hData.continuousFlickVelocity > 0))
1452 && qAbs(t: hVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) {
1453 // accelerate flick for large view flicked quickly
1454 canBoost = true;
1455 }
1456
1457 flickBoost = canBoost ? qBound(min: 1.0, val: flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0;
1458 const int flickThreshold = QGuiApplicationPrivate::platformIntegration()->styleHint(hint: QPlatformIntegration::FlickStartDistance).toInt();
1459
1460 bool flickedVertically = false;
1461 vVelocity *= flickBoost;
1462 bool isVerticalFlickAllowed = q->yflick() && qAbs(t: vVelocity) > _q_MinimumFlickVelocity && qAbs(t: pos.y() - pressPos.y()) > flickThreshold;
1463 if (isVerticalFlickAllowed) {
1464 velocityTimeline.reset(vData.smoothVelocity);
1465 vData.smoothVelocity.setValue(-vVelocity);
1466 flickedVertically = flickY(eventType: event->type(), velocity: vVelocity);
1467 }
1468
1469 bool flickedHorizontally = false;
1470 hVelocity *= flickBoost;
1471 bool isHorizontalFlickAllowed = q->xflick() && qAbs(t: hVelocity) > _q_MinimumFlickVelocity && qAbs(t: pos.x() - pressPos.x()) > flickThreshold;
1472 if (isHorizontalFlickAllowed) {
1473 velocityTimeline.reset(hData.smoothVelocity);
1474 hData.smoothVelocity.setValue(-hVelocity);
1475 flickedHorizontally = flickX(eventType: event->type(), velocity: hVelocity);
1476 }
1477
1478 if (!isVerticalFlickAllowed)
1479 fixupY();
1480
1481 if (!isHorizontalFlickAllowed)
1482 fixupX();
1483
1484 flickingStarted(flickingH: flickedHorizontally, flickingV: flickedVertically);
1485 if (!isViewMoving()) {
1486 q->movementEnding();
1487 } else {
1488 if (flickedVertically)
1489 vMoved = true;
1490 if (flickedHorizontally)
1491 hMoved = true;
1492 q->movementStarting();
1493 }
1494}
1495
1496void QQuickFlickable::mousePressEvent(QMouseEvent *event)
1497{
1498 Q_D(QQuickFlickable);
1499 if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) {
1500 if (!d->pressed)
1501 d->handlePressEvent(event);
1502 event->accept();
1503 } else {
1504 QQuickItem::mousePressEvent(event);
1505 }
1506}
1507
1508void QQuickFlickable::mouseMoveEvent(QMouseEvent *event)
1509{
1510 Q_D(QQuickFlickable);
1511 if (d->interactive && d->wantsPointerEvent(event)) {
1512 d->handleMoveEvent(event);
1513 event->accept();
1514 } else {
1515 QQuickItem::mouseMoveEvent(event);
1516 }
1517}
1518
1519void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
1520{
1521 Q_D(QQuickFlickable);
1522 if (d->interactive && d->wantsPointerEvent(event)) {
1523 if (d->delayedPressEvent) {
1524 d->replayDelayedPress();
1525
1526 // Now send the release
1527 if (auto grabber = qmlobject_cast<QQuickItem *>(object: event->exclusiveGrabber(point: event->point(i: 0)))) {
1528 // not copying or detaching anything, so make sure we return the original event unchanged
1529 const auto oldPosition = event->point(i: 0).position();
1530 QMutableEventPoint::setPosition(p&: event->point(i: 0), arg: grabber->mapFromScene(point: event->scenePosition()));
1531 QCoreApplication::sendEvent(receiver: window(), event);
1532 QMutableEventPoint::setPosition(p&: event->point(i: 0), arg: oldPosition);
1533 }
1534
1535 // And the event has been consumed
1536 d->stealMouse = false;
1537 d->pressed = false;
1538 return;
1539 }
1540
1541 d->handleReleaseEvent(event);
1542 event->accept();
1543 } else {
1544 QQuickItem::mouseReleaseEvent(event);
1545 }
1546}
1547
1548void QQuickFlickable::touchEvent(QTouchEvent *event)
1549{
1550 Q_D(QQuickFlickable);
1551 bool unhandled = false;
1552 const auto &firstPoint = event->points().first();
1553 switch (firstPoint.state()) {
1554 case QEventPoint::State::Pressed:
1555 if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) {
1556 if (!d->pressed)
1557 d->handlePressEvent(event);
1558 event->accept();
1559 } else {
1560 unhandled = true;
1561 }
1562 break;
1563 case QEventPoint::State::Updated:
1564 if (d->interactive && d->wantsPointerEvent(event)) {
1565 d->handleMoveEvent(event);
1566 event->accept();
1567 } else {
1568 unhandled = true;
1569 }
1570 break;
1571 case QEventPoint::State::Released:
1572 if (d->interactive && d->wantsPointerEvent(event)) {
1573 if (d->delayedPressEvent) {
1574 d->replayDelayedPress();
1575
1576 // Now send the release
1577 auto &firstPoint = event->point(i: 0);
1578 if (auto grabber = qmlobject_cast<QQuickItem *>(object: event->exclusiveGrabber(point: firstPoint))) {
1579 const auto localPos = grabber->mapFromScene(point: firstPoint.scenePosition());
1580 QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, transformedLocalPos: localPos));
1581 QCoreApplication::sendEvent(receiver: window(), event: localizedEvent.data());
1582 }
1583
1584 // And the event has been consumed
1585 d->stealMouse = false;
1586 d->pressed = false;
1587 return;
1588 }
1589
1590 d->handleReleaseEvent(event);
1591 event->accept();
1592 } else {
1593 unhandled = true;
1594 }
1595 break;
1596 case QEventPoint::State::Stationary:
1597 case QEventPoint::State::Unknown:
1598 break;
1599 }
1600 if (unhandled)
1601 QQuickItem::touchEvent(event);
1602}
1603
1604#if QT_CONFIG(wheelevent)
1605void QQuickFlickable::wheelEvent(QWheelEvent *event)
1606{
1607 Q_D(QQuickFlickable);
1608 if (!d->interactive || !d->wantsPointerEvent(event)) {
1609 QQuickItem::wheelEvent(event);
1610 return;
1611 }
1612 qCDebug(lcWheel) << event->device() << event << event->source();
1613 event->setAccepted(false);
1614 qint64 currentTimestamp = d->computeCurrentTime(event);
1615 switch (event->phase()) {
1616 case Qt::ScrollBegin:
1617 d->scrollingPhase = true;
1618 d->accumulatedWheelPixelDelta = QVector2D();
1619 d->vData.velocity = 0;
1620 d->hData.velocity = 0;
1621 d->timer.start();
1622 d->maybeBeginDrag(currentTimestamp, pressPosn: event->position());
1623 d->lastPosTime = -1;
1624 break;
1625 case Qt::NoScrollPhase: // default phase with an ordinary wheel mouse
1626 case Qt::ScrollUpdate:
1627 if (d->scrollingPhase)
1628 d->pressed = true;
1629 break;
1630 case Qt::ScrollMomentum:
1631 d->pressed = false;
1632 d->scrollingPhase = false;
1633 d->draggingEnding();
1634 if (isMoving())
1635 event->accept();
1636 d->lastPosTime = -1;
1637 break;
1638 case Qt::ScrollEnd:
1639 d->pressed = false;
1640 d->scrollingPhase = false;
1641 d->draggingEnding();
1642 event->accept();
1643 returnToBounds();
1644 d->lastPosTime = -1;
1645 d->stealMouse = false;
1646 if (!d->velocityTimeline.isActive() && !d->timeline.isActive())
1647 movementEnding(hMovementEnding: true, vMovementEnding: true);
1648 return;
1649 }
1650
1651 qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / qreal(1000);
1652 if (elapsed <= 0) {
1653 d->lastPosTime = currentTimestamp;
1654 qCDebug(lcWheel) << "insufficient elapsed time: can't calculate velocity" << elapsed;
1655 return;
1656 }
1657
1658 if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull() || event->phase() == Qt::NoScrollPhase) {
1659 // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta
1660 int xDelta = event->angleDelta().x();
1661 int yDelta = event->angleDelta().y();
1662
1663 if (d->wheelDeceleration > _q_MaximumWheelDeceleration) {
1664 const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24;
1665 // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel
1666 // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines()
1667 if (yflick() && yDelta != 0) {
1668 d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
1669 d->vMoved = true;
1670 qreal scrollPixel = (-yDelta / 120.0 * wheelScroll);
1671 if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
1672 const qreal estContentPos = scrollPixel + d->vData.move.value();
1673 if (scrollPixel > 0) { // Forward direction (away from user)
1674 if (d->vData.move.value() >= minYExtent())
1675 d->vMoved = false;
1676 else if (estContentPos > minYExtent())
1677 scrollPixel = minYExtent() - d->vData.move.value();
1678 } else { // Backward direction (towards user)
1679 if (d->vData.move.value() <= maxYExtent())
1680 d->vMoved = false;
1681 else if (estContentPos < maxYExtent())
1682 scrollPixel = maxYExtent() - d->vData.move.value();
1683 }
1684 }
1685 if (d->vMoved) {
1686 d->resetTimeline(data&: d->vData);
1687 movementStarting();
1688 d->timeline.moveBy(d->vData.move, change: scrollPixel, QEasingCurve(QEasingCurve::OutExpo), time: 3*d->fixupDuration/4);
1689 d->vData.fixingUp = true;
1690 d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d));
1691 }
1692 event->accept();
1693 }
1694 if (xflick() && xDelta != 0) {
1695 d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
1696 d->hMoved = true;
1697 qreal scrollPixel = (-xDelta / 120.0 * wheelScroll);
1698 if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
1699 const qreal estContentPos = scrollPixel + d->hData.move.value();
1700 if (scrollPixel > 0) { // Forward direction (away from user)
1701 if (d->hData.move.value() >= minXExtent())
1702 d->hMoved = false;
1703 else if (estContentPos > minXExtent())
1704 scrollPixel = minXExtent() - d->hData.move.value();
1705 } else { // Backward direction (towards user)
1706 if (d->hData.move.value() <= maxXExtent())
1707 d->hMoved = false;
1708 else if (estContentPos < maxXExtent())
1709 scrollPixel = maxXExtent() - d->hData.move.value();
1710 }
1711 }
1712 if (d->hMoved) {
1713 d->resetTimeline(data&: d->hData);
1714 movementStarting();
1715 d->timeline.moveBy(d->hData.move, change: scrollPixel, QEasingCurve(QEasingCurve::OutExpo), time: 3*d->fixupDuration/4);
1716 d->hData.fixingUp = true;
1717 d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d));
1718 }
1719 event->accept();
1720 }
1721 } else {
1722 // wheelDeceleration is set to some reasonable value: the user or the platform wants to have
1723 // the classic Qt Quick mouse wheel acceleration behavior.
1724 // For a single "clicky" wheel event (angleDelta +/- 120),
1725 // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines().
1726 // The decel algo from there is
1727 // qreal dist = v2 / (accel * 2.0);
1728 // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2)
1729 // now solve for dt:
1730 // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance)
1731 if (!isMoving())
1732 elapsed = 120 / qSqrt(v: d->wheelDeceleration * 2 * d->initialWheelFlickDistance);
1733 if (yflick() && yDelta != 0) {
1734 qreal instVelocity = yDelta / elapsed;
1735 // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
1736 if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0))
1737 d->vData.velocityBuffer.clear();
1738 d->vData.addVelocitySample(v: instVelocity, maxVelocity: d->maxVelocity);
1739 d->vData.updateVelocity();
1740 if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) {
1741 const bool newFlick = d->flickY(eventType: event->type(), velocity: d->vData.velocity);
1742 if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) {
1743 d->flickingStarted(flickingH: false, flickingV: true);
1744 d->vMoved = true;
1745 movementStarting();
1746 }
1747 event->accept();
1748 }
1749 }
1750 if (xflick() && xDelta != 0) {
1751 qreal instVelocity = xDelta / elapsed;
1752 // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
1753 if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0))
1754 d->hData.velocityBuffer.clear();
1755 d->hData.addVelocitySample(v: instVelocity, maxVelocity: d->maxVelocity);
1756 d->hData.updateVelocity();
1757 if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) {
1758 const bool newFlick = d->flickX(eventType: event->type(), velocity: d->hData.velocity);
1759 if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) {
1760 d->flickingStarted(flickingH: true, flickingV: false);
1761 d->hMoved = true;
1762 movementStarting();
1763 }
1764 event->accept();
1765 }
1766 }
1767 }
1768 } else {
1769 // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually
1770 int xDelta = event->pixelDelta().x();
1771 int yDelta = event->pixelDelta().y();
1772
1773 QVector2D velocity(xDelta / elapsed, yDelta / elapsed);
1774 d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta());
1775 // Try to drag if 1) we already are dragging or flicking, or
1776 // 2) the flickable is free to flick both directions, or
1777 // 3) the movement so far has been mostly horizontal AND it's free to flick horizontally, or
1778 // 4) the movement so far has been mostly vertical AND it's free to flick vertically.
1779 // Otherwise, wait until the next event. Wheel events with pixel deltas tend to come frequently.
1780 if (isMoving() || isFlicking() || (yflick() && xflick())
1781 || (xflick() && qAbs(t: d->accumulatedWheelPixelDelta.x()) > qAbs(t: d->accumulatedWheelPixelDelta.y() * 2))
1782 || (yflick() && qAbs(t: d->accumulatedWheelPixelDelta.y()) > qAbs(t: d->accumulatedWheelPixelDelta.x() * 2))) {
1783 d->drag(currentTimestamp, eventType: event->type(), localPos: event->position(), deltas: d->accumulatedWheelPixelDelta,
1784 overThreshold: true, momentum: !d->scrollingPhase, velocitySensitiveOverBounds: true, velocity);
1785 event->accept();
1786 } else {
1787 qCDebug(lcWheel) << "not dragging: accumulated deltas" << d->accumulatedWheelPixelDelta <<
1788 "moving?" << isMoving() << "can flick horizontally?" << xflick() << "vertically?" << yflick();
1789 }
1790 }
1791 d->lastPosTime = currentTimestamp;
1792
1793 if (!event->isAccepted())
1794 QQuickItem::wheelEvent(event);
1795}
1796#endif
1797
1798bool QQuickFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const
1799{
1800 Q_Q(const QQuickFlickable);
1801 QQuickItem *item = i;
1802 while (item) {
1803 QQuickFlickable *flick = qobject_cast<QQuickFlickable*>(object: item);
1804 if (flick && flick->pressDelay() > 0 && flick->isInteractive()) {
1805 // Found the innermost flickable with press delay - is it me?
1806 return (flick == q);
1807 }
1808 item = item->parentItem();
1809 }
1810 return false;
1811}
1812
1813void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent *event)
1814{
1815 Q_Q(QQuickFlickable);
1816 if (!q->window() || pressDelay <= 0)
1817 return;
1818
1819 // Only the innermost flickable should handle the delayed press; this allows
1820 // flickables up the parent chain to all see the events in their filter functions
1821 if (!isInnermostPressDelay(i: item))
1822 return;
1823
1824 delayedPressEvent = QQuickDeliveryAgentPrivate::clonePointerEvent(event);
1825 delayedPressEvent->setAccepted(false);
1826 delayedPressTimer.start(msec: pressDelay, obj: q);
1827 qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent;
1828}
1829
1830void QQuickFlickablePrivate::clearDelayedPress()
1831{
1832 if (delayedPressEvent) {
1833 delayedPressTimer.stop();
1834 qCDebug(lcReplay) << "clear delayed press" << delayedPressEvent;
1835 delete delayedPressEvent;
1836 delayedPressEvent = nullptr;
1837 }
1838}
1839
1840void QQuickFlickablePrivate::replayDelayedPress()
1841{
1842 Q_Q(QQuickFlickable);
1843 if (delayedPressEvent) {
1844 // Losing the grab will clear the delayed press event; take control of it here
1845 QScopedPointer<QPointerEvent> event(delayedPressEvent);
1846 delayedPressEvent = nullptr;
1847 delayedPressTimer.stop();
1848
1849 // If we have the grab, release before delivering the event
1850 if (QQuickWindow *window = q->window()) {
1851 auto da = deliveryAgentPrivate();
1852 da->allowChildEventFiltering = false; // don't allow re-filtering during replay
1853 replayingPressEvent = true;
1854 auto &firstPoint = event->point(i: 0);
1855 // At first glance, it's weird for delayedPressEvent to already have a grabber;
1856 // but on press, filterMouseEvent() took the exclusive grab, and that's stored
1857 // in the device-specific EventPointData instance in QPointingDevicePrivate::activePoints,
1858 // not in the event itself. If this Flickable is still the grabber of that point on that device,
1859 // that's the reason; but now it doesn't need that grab anymore.
1860 if (event->exclusiveGrabber(point: firstPoint) == q)
1861 event->setExclusiveGrabber(point: firstPoint, exclusiveGrabber: nullptr);
1862
1863 qCDebug(lcReplay) << "replaying" << event.data();
1864 // Put scenePosition into position, for the sake of QQuickWindowPrivate::translateTouchEvent()
1865 // TODO remove this if we remove QQuickWindowPrivate::translateTouchEvent()
1866 QMutableEventPoint::setPosition(p&: firstPoint, arg: firstPoint.scenePosition());
1867 // Send it through like a fresh press event, and let QQuickWindow
1868 // (more specifically, QQuickWindowPrivate::deliverPressOrReleaseEvent)
1869 // find the item or handler that should receive it, as usual.
1870 QCoreApplication::sendEvent(receiver: window, event: event.data());
1871 qCDebug(lcReplay) << "replay done";
1872
1873 // We're done with replay, go back to normal delivery behavior
1874 replayingPressEvent = false;
1875 da->allowChildEventFiltering = true;
1876 }
1877 }
1878}
1879
1880//XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned.
1881
1882/*!
1883 \internal
1884
1885 This function is called from the timeline,
1886 when advancement in the timeline is modifying the hData.move value.
1887 The \a x argument is the newly updated value in hData.move.
1888 The purpose of the function is to update the x position of the contentItem.
1889*/
1890void QQuickFlickablePrivate::setViewportX(qreal x)
1891{
1892 Q_Q(QQuickFlickable);
1893 qreal effectiveX = pixelAligned ? -Round(t: -x) : x;
1894
1895 const qreal maxX = q->maxXExtent();
1896 const qreal minX = q->minXExtent();
1897
1898 if (boundsMovement == int(QQuickFlickable::StopAtBounds))
1899 effectiveX = qBound(min: maxX, val: effectiveX, max: minX);
1900
1901 contentItem->setX(effectiveX);
1902 if (contentItem->x() != effectiveX)
1903 return; // reentered
1904
1905 qreal overshoot = 0.0;
1906 if (x <= maxX)
1907 overshoot = maxX - x;
1908 else if (x >= minX)
1909 overshoot = minX - x;
1910
1911 if (overshoot != hData.overshoot) {
1912 hData.overshoot = overshoot;
1913 emit q->horizontalOvershootChanged();
1914 }
1915}
1916
1917/*!
1918 \internal
1919
1920 This function is called from the timeline,
1921 when advancement in the timeline is modifying the vData.move value.
1922 The \a y argument is the newly updated value in vData.move.
1923 The purpose of the function is to update the y position of the contentItem.
1924*/
1925void QQuickFlickablePrivate::setViewportY(qreal y)
1926{
1927 Q_Q(QQuickFlickable);
1928 qreal effectiveY = pixelAligned ? -Round(t: -y) : y;
1929
1930 const qreal maxY = q->maxYExtent();
1931 const qreal minY = q->minYExtent();
1932
1933 if (boundsMovement == int(QQuickFlickable::StopAtBounds))
1934 effectiveY = qBound(min: maxY, val: effectiveY, max: minY);
1935
1936 contentItem->setY(effectiveY);
1937 if (contentItem->y() != effectiveY)
1938 return; // reentered
1939
1940 qreal overshoot = 0.0;
1941 if (y <= maxY)
1942 overshoot = maxY - y;
1943 else if (y >= minY)
1944 overshoot = minY - y;
1945
1946 if (overshoot != vData.overshoot) {
1947 vData.overshoot = overshoot;
1948 emit q->verticalOvershootChanged();
1949 }
1950}
1951
1952void QQuickFlickable::timerEvent(QTimerEvent *event)
1953{
1954 Q_D(QQuickFlickable);
1955 if (event->timerId() == d->delayedPressTimer.timerId()) {
1956 d->delayedPressTimer.stop();
1957 if (d->delayedPressEvent) {
1958 d->replayDelayedPress();
1959 }
1960 }
1961}
1962
1963qreal QQuickFlickable::minYExtent() const
1964{
1965 Q_D(const QQuickFlickable);
1966 return d->vData.startMargin;
1967}
1968
1969qreal QQuickFlickable::minXExtent() const
1970{
1971 Q_D(const QQuickFlickable);
1972 return d->hData.startMargin;
1973}
1974
1975/* returns -ve */
1976qreal QQuickFlickable::maxXExtent() const
1977{
1978 Q_D(const QQuickFlickable);
1979 return qMin<qreal>(a: minXExtent(), b: width() - vWidth() - d->hData.endMargin);
1980}
1981/* returns -ve */
1982qreal QQuickFlickable::maxYExtent() const
1983{
1984 Q_D(const QQuickFlickable);
1985 return qMin<qreal>(a: minYExtent(), b: height() - vHeight() - d->vData.endMargin);
1986}
1987
1988void QQuickFlickable::componentComplete()
1989{
1990 Q_D(QQuickFlickable);
1991 QQuickItem::componentComplete();
1992 if (!d->hData.explicitValue && d->hData.startMargin != 0.)
1993 setContentX(-minXExtent());
1994 if (!d->vData.explicitValue && d->vData.startMargin != 0.)
1995 setContentY(-minYExtent());
1996 if (lcWheel().isDebugEnabled() || lcVel().isDebugEnabled()) {
1997 d->timeline.setObjectName(QLatin1String("timeline for Flickable ") + objectName());
1998 d->velocityTimeline.setObjectName(QLatin1String("velocity timeline for Flickable ") + objectName());
1999 }
2000}
2001
2002void QQuickFlickable::viewportMoved(Qt::Orientations orient)
2003{
2004 Q_D(QQuickFlickable);
2005 if (orient & Qt::Vertical)
2006 d->viewportAxisMoved(data&: d->vData, minExtent: minYExtent(), maxExtent: maxYExtent(), fixupCallback: d->fixupY_callback);
2007 if (orient & Qt::Horizontal)
2008 d->viewportAxisMoved(data&: d->hData, minExtent: minXExtent(), maxExtent: maxXExtent(), fixupCallback: d->fixupX_callback);
2009 d->updateBeginningEnd();
2010}
2011
2012void QQuickFlickablePrivate::viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent,
2013 QQuickTimeLineCallback::Callback fixupCallback)
2014{
2015 if (!scrollingPhase && (pressed || calcVelocity)) {
2016 int elapsed = data.velocityTime.restart();
2017 if (elapsed > 0) {
2018 qreal velocity = (data.lastPos - data.move.value()) * 1000 / elapsed;
2019 if (qAbs(t: velocity) > 0) {
2020 velocityTimeline.reset(data.smoothVelocity);
2021 velocityTimeline.set(data.smoothVelocity, velocity);
2022 qCDebug(lcVel) << "touchpad scroll phase: velocity" << velocity;
2023 }
2024 }
2025 } else {
2026 if (timeline.time() > data.vTime) {
2027 velocityTimeline.reset(data.smoothVelocity);
2028 int dt = timeline.time() - data.vTime;
2029 if (dt > 2) {
2030 qreal velocity = (data.lastPos - data.move.value()) * 1000 / dt;
2031 if (!qFuzzyCompare(p1: data.smoothVelocity.value(), p2: velocity))
2032 qCDebug(lcVel) << "velocity" << data.smoothVelocity.value() << "->" << velocity
2033 << "computed as (" << data.lastPos << "-" << data.move.value() << ") * 1000 / ("
2034 << timeline.time() << "-" << data.vTime << ")";
2035 data.smoothVelocity.setValue(velocity);
2036 }
2037 }
2038 }
2039
2040 if (!data.inOvershoot && !data.fixingUp && data.flicking
2041 && (data.move.value() > minExtent || data.move.value() < maxExtent)
2042 && qAbs(t: data.smoothVelocity.value()) > 10) {
2043 // Increase deceleration if we've passed a bound
2044 qreal overBound = data.move.value() > minExtent
2045 ? data.move.value() - minExtent
2046 : maxExtent - data.move.value();
2047 data.inOvershoot = true;
2048 qreal maxDistance = overShootDistance(velocity: qAbs(t: data.smoothVelocity.value())) - overBound;
2049 resetTimeline(data);
2050 if (maxDistance > 0)
2051 timeline.accel(data.move, velocity: -data.smoothVelocity.value(), accel: deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
2052 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
2053 }
2054
2055 data.lastPos = data.move.value();
2056 data.vTime = timeline.time();
2057}
2058
2059void QQuickFlickable::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2060{
2061 Q_D(QQuickFlickable);
2062 QQuickItem::geometryChange(newGeometry, oldGeometry);
2063
2064 bool changed = false;
2065 if (newGeometry.width() != oldGeometry.width()) {
2066 changed = true; // we must update visualArea.widthRatio
2067 if (d->hData.viewSize < 0)
2068 d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin);
2069 // Make sure that we're entirely in view.
2070 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2071 d->fixupMode = QQuickFlickablePrivate::Immediate;
2072 d->fixupX();
2073 }
2074 }
2075 if (newGeometry.height() != oldGeometry.height()) {
2076 changed = true; // we must update visualArea.heightRatio
2077 if (d->vData.viewSize < 0)
2078 d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin);
2079 // Make sure that we're entirely in view.
2080 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2081 d->fixupMode = QQuickFlickablePrivate::Immediate;
2082 d->fixupY();
2083 }
2084 }
2085
2086 if (changed)
2087 d->updateBeginningEnd();
2088}
2089
2090/*!
2091 \qmlmethod QtQuick::Flickable::flick(qreal xVelocity, qreal yVelocity)
2092
2093 Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec.
2094
2095 Calling this method will update the corresponding moving and flicking properties and signals,
2096 just like a real touchscreen flick.
2097*/
2098
2099void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity)
2100{
2101 Q_D(QQuickFlickable);
2102 d->hData.reset();
2103 d->vData.reset();
2104 d->hData.velocity = xVelocity;
2105 d->vData.velocity = yVelocity;
2106 d->hData.vTime = d->vData.vTime = d->timeline.time();
2107
2108 const bool flickedX = xflick() && !qFuzzyIsNull(d: xVelocity) && d->flickX(eventType: QEvent::TouchUpdate, velocity: xVelocity);
2109 const bool flickedY = yflick() && !qFuzzyIsNull(d: yVelocity) && d->flickY(eventType: QEvent::TouchUpdate, velocity: yVelocity);
2110
2111 if (flickedX)
2112 d->hMoved = true;
2113 if (flickedY)
2114 d->vMoved = true;
2115 movementStarting();
2116 d->flickingStarted(flickingH: flickedX, flickingV: flickedY);
2117}
2118
2119void QQuickFlickablePrivate::flickingStarted(bool flickingH, bool flickingV)
2120{
2121 Q_Q(QQuickFlickable);
2122 if (!flickingH && !flickingV)
2123 return;
2124
2125 bool wasFlicking = hData.flicking || vData.flicking;
2126 if (flickingH && !hData.flicking) {
2127 hData.flicking = true;
2128 emit q->flickingHorizontallyChanged();
2129 }
2130 if (flickingV && !vData.flicking) {
2131 vData.flicking = true;
2132 emit q->flickingVerticallyChanged();
2133 }
2134 if (!wasFlicking && (hData.flicking || vData.flicking)) {
2135 emit q->flickingChanged();
2136 emit q->flickStarted();
2137 }
2138}
2139
2140/*!
2141 \qmlmethod QtQuick::Flickable::cancelFlick()
2142
2143 Cancels the current flick animation.
2144*/
2145
2146void QQuickFlickable::cancelFlick()
2147{
2148 Q_D(QQuickFlickable);
2149 d->resetTimeline(data&: d->hData);
2150 d->resetTimeline(data&: d->vData);
2151 movementEnding();
2152}
2153
2154void QQuickFlickablePrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
2155{
2156 if (!prop || !prop->data)
2157 return;
2158
2159 if (QQuickItem *i = qmlobject_cast<QQuickItem *>(object: o)) {
2160 i->setParentItem(static_cast<QQuickFlickablePrivate*>(prop->data)->contentItem);
2161 } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(object: o)) {
2162 static_cast<QQuickFlickablePrivate*>(prop->data)->addPointerHandler(h: pointerHandler);
2163 } else {
2164 o->setParent(prop->object); // XXX todo - do we want this?
2165 }
2166}
2167
2168qsizetype QQuickFlickablePrivate::data_count(QQmlListProperty<QObject> *)
2169{
2170 // XXX todo
2171 return 0;
2172}
2173
2174QObject *QQuickFlickablePrivate::data_at(QQmlListProperty<QObject> *, qsizetype)
2175{
2176 // XXX todo
2177 return nullptr;
2178}
2179
2180void QQuickFlickablePrivate::data_clear(QQmlListProperty<QObject> *)
2181{
2182 // XXX todo
2183}
2184
2185QQmlListProperty<QObject> QQuickFlickable::flickableData()
2186{
2187 Q_D(QQuickFlickable);
2188 return QQmlListProperty<QObject>(this, (void *)d, QQuickFlickablePrivate::data_append,
2189 QQuickFlickablePrivate::data_count,
2190 QQuickFlickablePrivate::data_at,
2191 QQuickFlickablePrivate::data_clear);
2192}
2193
2194QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren()
2195{
2196 Q_D(QQuickFlickable);
2197 return QQuickItemPrivate::get(item: d->contentItem)->children();
2198}
2199
2200/*!
2201 \qmlproperty enumeration QtQuick::Flickable::boundsBehavior
2202 This property holds whether the surface may be dragged
2203 beyond the Flickable's boundaries, or overshoot the
2204 Flickable's boundaries when flicked.
2205
2206 When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value
2207 other than \c Flickable.StopAtBounds will give a feeling that the edges of
2208 the view are soft, rather than a hard physical boundary.
2209
2210 The \c boundsBehavior can be one of:
2211
2212 \list
2213 \li Flickable.StopAtBounds - the contents can not be dragged beyond the boundary
2214 of the flickable, and flicks will not overshoot.
2215 \li Flickable.DragOverBounds - the contents can be dragged beyond the boundary
2216 of the Flickable, but flicks will not overshoot.
2217 \li Flickable.OvershootBounds - the contents can overshoot the boundary when flicked,
2218 but the content cannot be dragged beyond the boundary of the flickable. (since \c{QtQuick 2.5})
2219 \li Flickable.DragAndOvershootBounds (default) - the contents can be dragged
2220 beyond the boundary of the Flickable, and can overshoot the
2221 boundary when flicked.
2222 \endlist
2223
2224 \sa horizontalOvershoot, verticalOvershoot, boundsMovement
2225*/
2226QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const
2227{
2228 Q_D(const QQuickFlickable);
2229 return d->boundsBehavior;
2230}
2231
2232void QQuickFlickable::setBoundsBehavior(BoundsBehavior b)
2233{
2234 Q_D(QQuickFlickable);
2235 if (b == d->boundsBehavior)
2236 return;
2237 d->boundsBehavior = b;
2238 emit boundsBehaviorChanged();
2239}
2240
2241/*!
2242 \qmlproperty Transition QtQuick::Flickable::rebound
2243
2244 This holds the transition to be applied to the content view when
2245 it snaps back to the bounds of the flickable. The transition is
2246 triggered when the view is flicked or dragged past the edge of the
2247 content area, or when returnToBounds() is called.
2248
2249 \qml
2250 import QtQuick 2.0
2251
2252 Flickable {
2253 width: 150; height: 150
2254 contentWidth: 300; contentHeight: 300
2255
2256 rebound: Transition {
2257 NumberAnimation {
2258 properties: "x,y"
2259 duration: 1000
2260 easing.type: Easing.OutBounce
2261 }
2262 }
2263
2264 Rectangle {
2265 width: 300; height: 300
2266 gradient: Gradient {
2267 GradientStop { position: 0.0; color: "lightsteelblue" }
2268 GradientStop { position: 1.0; color: "blue" }
2269 }
2270 }
2271 }
2272 \endqml
2273
2274 When the above view is flicked beyond its bounds, it will return to its
2275 bounds using the transition specified:
2276
2277 \image flickable-rebound.gif
2278
2279 If this property is not set, a default animation is applied.
2280 */
2281QQuickTransition *QQuickFlickable::rebound() const
2282{
2283 Q_D(const QQuickFlickable);
2284 return d->rebound;
2285}
2286
2287void QQuickFlickable::setRebound(QQuickTransition *transition)
2288{
2289 Q_D(QQuickFlickable);
2290 if (transition) {
2291 if (!d->hData.transitionToBounds)
2292 d->hData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("x"));
2293 if (!d->vData.transitionToBounds)
2294 d->vData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("y"));
2295 }
2296 if (d->rebound != transition) {
2297 d->rebound = transition;
2298 emit reboundChanged();
2299 }
2300}
2301
2302/*!
2303 \qmlproperty real QtQuick::Flickable::contentWidth
2304 \qmlproperty real QtQuick::Flickable::contentHeight
2305
2306 The dimensions of the content (the surface controlled by Flickable).
2307 This should typically be set to the combined size of the items placed in the
2308 Flickable.
2309
2310 The following snippet shows how these properties are used to display
2311 an image that is larger than the Flickable item itself:
2312
2313 \snippet qml/flickable.qml document
2314
2315 In some cases, the content dimensions can be automatically set
2316 based on the \l {Item::childrenRect.width}{childrenRect.width}
2317 and \l {Item::childrenRect.height}{childrenRect.height} properties
2318 of the \l contentItem. For example, the previous snippet could be rewritten with:
2319
2320 \code
2321 contentWidth: contentItem.childrenRect.width; contentHeight: contentItem.childrenRect.height
2322 \endcode
2323
2324 Though this assumes that the origin of the childrenRect is 0,0.
2325*/
2326qreal QQuickFlickable::contentWidth() const
2327{
2328 Q_D(const QQuickFlickable);
2329 return d->hData.viewSize;
2330}
2331
2332void QQuickFlickable::setContentWidth(qreal w)
2333{
2334 Q_D(QQuickFlickable);
2335 if (d->hData.viewSize == w)
2336 return;
2337 d->hData.viewSize = w;
2338 if (w < 0)
2339 d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin);
2340 else
2341 d->contentItem->setWidth(w);
2342 d->hData.markExtentsDirty();
2343 // Make sure that we're entirely in view.
2344 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2345 d->fixupMode = QQuickFlickablePrivate::Immediate;
2346 d->fixupX();
2347 } else if (!d->pressed && d->hData.fixingUp) {
2348 d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
2349 d->fixupX();
2350 }
2351 emit contentWidthChanged();
2352 d->updateBeginningEnd();
2353}
2354
2355qreal QQuickFlickable::contentHeight() const
2356{
2357 Q_D(const QQuickFlickable);
2358 return d->vData.viewSize;
2359}
2360
2361void QQuickFlickable::setContentHeight(qreal h)
2362{
2363 Q_D(QQuickFlickable);
2364 if (d->vData.viewSize == h)
2365 return;
2366 d->vData.viewSize = h;
2367 if (h < 0)
2368 d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin);
2369 else
2370 d->contentItem->setHeight(h);
2371 d->vData.markExtentsDirty();
2372 // Make sure that we're entirely in view.
2373 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2374 d->fixupMode = QQuickFlickablePrivate::Immediate;
2375 d->fixupY();
2376 } else if (!d->pressed && d->vData.fixingUp) {
2377 d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
2378 d->fixupY();
2379 }
2380 emit contentHeightChanged();
2381 d->updateBeginningEnd();
2382}
2383
2384/*!
2385 \qmlproperty real QtQuick::Flickable::topMargin
2386 \qmlproperty real QtQuick::Flickable::leftMargin
2387 \qmlproperty real QtQuick::Flickable::bottomMargin
2388 \qmlproperty real QtQuick::Flickable::rightMargin
2389
2390 These properties hold the margins around the content. This space is reserved
2391 in addition to the contentWidth and contentHeight.
2392*/
2393
2394
2395qreal QQuickFlickable::topMargin() const
2396{
2397 Q_D(const QQuickFlickable);
2398 return d->vData.startMargin;
2399}
2400
2401void QQuickFlickable::setTopMargin(qreal m)
2402{
2403 Q_D(QQuickFlickable);
2404 if (d->vData.startMargin == m)
2405 return;
2406 d->vData.startMargin = m;
2407 d->vData.markExtentsDirty();
2408 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2409 d->fixupMode = QQuickFlickablePrivate::Immediate;
2410 d->fixupY();
2411 }
2412 emit topMarginChanged();
2413 d->updateBeginningEnd();
2414}
2415
2416qreal QQuickFlickable::bottomMargin() const
2417{
2418 Q_D(const QQuickFlickable);
2419 return d->vData.endMargin;
2420}
2421
2422void QQuickFlickable::setBottomMargin(qreal m)
2423{
2424 Q_D(QQuickFlickable);
2425 if (d->vData.endMargin == m)
2426 return;
2427 d->vData.endMargin = m;
2428 d->vData.markExtentsDirty();
2429 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2430 d->fixupMode = QQuickFlickablePrivate::Immediate;
2431 d->fixupY();
2432 }
2433 emit bottomMarginChanged();
2434 d->updateBeginningEnd();
2435}
2436
2437qreal QQuickFlickable::leftMargin() const
2438{
2439 Q_D(const QQuickFlickable);
2440 return d->hData.startMargin;
2441}
2442
2443void QQuickFlickable::setLeftMargin(qreal m)
2444{
2445 Q_D(QQuickFlickable);
2446 if (d->hData.startMargin == m)
2447 return;
2448 d->hData.startMargin = m;
2449 d->hData.markExtentsDirty();
2450 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2451 d->fixupMode = QQuickFlickablePrivate::Immediate;
2452 d->fixupX();
2453 }
2454 emit leftMarginChanged();
2455 d->updateBeginningEnd();
2456}
2457
2458qreal QQuickFlickable::rightMargin() const
2459{
2460 Q_D(const QQuickFlickable);
2461 return d->hData.endMargin;
2462}
2463
2464void QQuickFlickable::setRightMargin(qreal m)
2465{
2466 Q_D(QQuickFlickable);
2467 if (d->hData.endMargin == m)
2468 return;
2469 d->hData.endMargin = m;
2470 d->hData.markExtentsDirty();
2471 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2472 d->fixupMode = QQuickFlickablePrivate::Immediate;
2473 d->fixupX();
2474 }
2475 emit rightMarginChanged();
2476 d->updateBeginningEnd();
2477}
2478
2479/*!
2480 \qmlproperty real QtQuick::Flickable::originX
2481 \qmlproperty real QtQuick::Flickable::originY
2482
2483 These properties hold the origin of the content. This value always refers
2484 to the top-left position of the content regardless of layout direction.
2485
2486 This is usually (0,0), however ListView and GridView may have an arbitrary
2487 origin due to delegate size variation, or item insertion/removal outside
2488 the visible region.
2489
2490 \sa contentX, contentY
2491*/
2492
2493qreal QQuickFlickable::originY() const
2494{
2495 Q_D(const QQuickFlickable);
2496 return -minYExtent() + d->vData.startMargin;
2497}
2498
2499qreal QQuickFlickable::originX() const
2500{
2501 Q_D(const QQuickFlickable);
2502 return -minXExtent() + d->hData.startMargin;
2503}
2504
2505
2506/*!
2507 \qmlmethod QtQuick::Flickable::resizeContent(real width, real height, QPointF center)
2508
2509 Resizes the content to \a width x \a height about \a center.
2510
2511 This does not scale the contents of the Flickable - it only resizes the \l contentWidth
2512 and \l contentHeight.
2513
2514 Resizing the content may result in the content being positioned outside
2515 the bounds of the Flickable. Calling \l returnToBounds() will
2516 move the content back within legal bounds.
2517*/
2518void QQuickFlickable::resizeContent(qreal w, qreal h, QPointF center)
2519{
2520 Q_D(QQuickFlickable);
2521 const qreal oldHSize = d->hData.viewSize;
2522 const qreal oldVSize = d->vData.viewSize;
2523 const bool needToUpdateWidth = w != oldHSize;
2524 const bool needToUpdateHeight = h != oldVSize;
2525 d->hData.viewSize = w;
2526 d->vData.viewSize = h;
2527 d->contentItem->setSize(QSizeF(w, h));
2528 if (needToUpdateWidth)
2529 emit contentWidthChanged();
2530 if (needToUpdateHeight)
2531 emit contentHeightChanged();
2532
2533 if (center.x() != 0) {
2534 qreal pos = center.x() * w / oldHSize;
2535 setContentX(contentX() + pos - center.x());
2536 }
2537 if (center.y() != 0) {
2538 qreal pos = center.y() * h / oldVSize;
2539 setContentY(contentY() + pos - center.y());
2540 }
2541 d->updateBeginningEnd();
2542}
2543
2544/*!
2545 \qmlmethod QtQuick::Flickable::returnToBounds()
2546
2547 Ensures the content is within legal bounds.
2548
2549 This may be called to ensure that the content is within legal bounds
2550 after manually positioning the content.
2551*/
2552void QQuickFlickable::returnToBounds()
2553{
2554 Q_D(QQuickFlickable);
2555 d->fixupX();
2556 d->fixupY();
2557}
2558
2559qreal QQuickFlickable::vWidth() const
2560{
2561 Q_D(const QQuickFlickable);
2562 if (d->hData.viewSize < 0)
2563 return width();
2564 else
2565 return d->hData.viewSize;
2566}
2567
2568qreal QQuickFlickable::vHeight() const
2569{
2570 Q_D(const QQuickFlickable);
2571 if (d->vData.viewSize < 0)
2572 return height();
2573 else
2574 return d->vData.viewSize;
2575}
2576
2577/*!
2578 \internal
2579
2580 The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along.
2581
2582 \return true if the flickable is allowed to flick in the horizontal direction, otherwise returns false
2583*/
2584bool QQuickFlickable::xflick() const
2585{
2586 Q_D(const QQuickFlickable);
2587 const int contentWidthWithMargins = d->contentItem->width() + d->hData.startMargin + d->hData.endMargin;
2588 if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentWidthWithMargins > width()))
2589 return true;
2590 if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
2591 return std::floor(x: qAbs(t: contentWidthWithMargins - width()));
2592 return d->flickableDirection & QQuickFlickable::HorizontalFlick;
2593}
2594
2595/*!
2596 \internal
2597
2598 The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along.
2599
2600 \return true if the flickable is allowed to flick in the vertical direction, otherwise returns false.
2601*/
2602bool QQuickFlickable::yflick() const
2603{
2604 Q_D(const QQuickFlickable);
2605 const int contentHeightWithMargins = d->contentItem->height() + d->vData.startMargin + d->vData.endMargin;
2606 if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentHeightWithMargins > height()))
2607 return true;
2608 if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
2609 return std::floor(x: qAbs(t: contentHeightWithMargins - height()));
2610 return d->flickableDirection & QQuickFlickable::VerticalFlick;
2611}
2612
2613void QQuickFlickable::mouseUngrabEvent()
2614{
2615 Q_D(QQuickFlickable);
2616 // if our mouse grab has been removed (probably by another Flickable),
2617 // fix our state
2618 if (!d->replayingPressEvent)
2619 d->cancelInteraction();
2620}
2621
2622void QQuickFlickablePrivate::cancelInteraction()
2623{
2624 Q_Q(QQuickFlickable);
2625 if (pressed) {
2626 clearDelayedPress();
2627 pressed = false;
2628 draggingEnding();
2629 stealMouse = false;
2630 q->setKeepMouseGrab(false);
2631 fixupX();
2632 fixupY();
2633 if (!isViewMoving())
2634 q->movementEnding();
2635 }
2636}
2637
2638void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h)
2639{
2640 Q_Q(const QQuickFlickable);
2641 qCDebug(lcHandlerParent) << "reparenting handler" << h << "to contentItem of" << q;
2642 h->setParent(contentItem);
2643 QQuickItemPrivate::get(item: contentItem)->addPointerHandler(h);
2644}
2645
2646/*! \internal
2647 QQuickFlickable::filterPointerEvent filters pointer events intercepted on the way
2648 to the child \a receiver, and potentially steals the exclusive grab.
2649
2650 This is how flickable takes over the handling of events from child items.
2651
2652 Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver.
2653*/
2654bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
2655{
2656 const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(ev: event);
2657 if (!(QQuickDeliveryAgentPrivate::isMouseEvent(ev: event) || isTouch ||
2658 QQuickDeliveryAgentPrivate::isTabletEvent(ev: event)))
2659 return false; // don't filter hover events or wheel events, for example
2660 Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself");
2661 Q_D(QQuickFlickable);
2662 // If a touch event contains a new press point, don't steal right away: watch the movements for a while
2663 if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(flag: QEventPoint::State::Pressed))
2664 d->stealMouse = false;
2665 // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child
2666 if (event->pointCount() > 1) {
2667 qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver;
2668 d->stealMouse = false;
2669 } else {
2670 qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver;
2671 }
2672
2673 const auto &firstPoint = event->points().first();
2674
2675 if (event->pointCount() == 1 && event->exclusiveGrabber(point: firstPoint) == this) {
2676 // We have an exclusive grab (since we're e.g dragging), but at the same time, we have
2677 // a child with a passive grab (which is why this filter is being called). And because
2678 // of that, we end up getting the same pointer events twice; First in our own event
2679 // handlers (because of the grab), then once more in here, since we filter the child.
2680 // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more
2681 // from below), we mark the event as filtered, and simply return.
2682 event->setAccepted(true);
2683 return true;
2684 }
2685
2686 QPointF localPos = mapFromScene(point: firstPoint.scenePosition());
2687 bool receiverDisabled = receiver && !receiver->isEnabled();
2688 bool stealThisEvent = d->stealMouse;
2689 bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab());
2690 bool receiverRelinquishGrab = false;
2691
2692 // Special case for MouseArea, try to guess what it does with the event
2693 if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(object: receiver)) {
2694 bool preventStealing = mouseArea->preventStealing();
2695#if QT_CONFIG(quick_draganddrop)
2696 if (mouseArea->drag() && mouseArea->drag()->target())
2697 preventStealing = true;
2698#endif
2699 if (!preventStealing && receiverKeepsGrab) {
2700 receiverRelinquishGrab = !receiverDisabled
2701 || (QQuickDeliveryAgentPrivate::isMouseEvent(ev: event)
2702 && firstPoint.state() == QEventPoint::State::Pressed
2703 && (receiver->acceptedMouseButtons() & static_cast<QMouseEvent *>(event)->button()));
2704 if (receiverRelinquishGrab)
2705 receiverKeepsGrab = false;
2706 }
2707 }
2708
2709 if ((stealThisEvent || contains(point: localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) {
2710 QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, transformedLocalPos: localPos));
2711 localizedEvent->setAccepted(false);
2712 switch (firstPoint.state()) {
2713 case QEventPoint::State::Updated:
2714 d->handleMoveEvent(event: localizedEvent.data());
2715 break;
2716 case QEventPoint::State::Pressed:
2717 d->handlePressEvent(event: localizedEvent.data());
2718 d->captureDelayedPress(item: receiver, event);
2719 // never grab the pointing device on press during filtering: do it later, during a move
2720 d->stealMouse = false;
2721 stealThisEvent = false;
2722 break;
2723 case QEventPoint::State::Released:
2724 d->handleReleaseEvent(event: localizedEvent.data());
2725 stealThisEvent = d->stealMouse;
2726 break;
2727 case QEventPoint::State::Stationary:
2728 case QEventPoint::State::Unknown:
2729 break;
2730 }
2731 if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) {
2732 d->clearDelayedPress();
2733 event->setExclusiveGrabber(point: firstPoint, exclusiveGrabber: this);
2734 } else if (d->delayedPressEvent) {
2735 event->setExclusiveGrabber(point: firstPoint, exclusiveGrabber: this);
2736 }
2737
2738 const bool filtered = !receiverRelinquishGrab && (stealThisEvent || d->delayedPressEvent || receiverDisabled);
2739 if (filtered) {
2740 event->setAccepted(true);
2741 }
2742 return filtered;
2743 } else if (d->lastPosTime != -1) {
2744 d->lastPosTime = -1;
2745 returnToBounds();
2746 }
2747 if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) {
2748 // mouse released, or another item has claimed the grab
2749 d->lastPosTime = -1;
2750 d->clearDelayedPress();
2751 d->stealMouse = false;
2752 d->pressed = false;
2753 }
2754 return false;
2755}
2756
2757/*! \internal
2758 Despite the name, this function filters all pointer events on their way to any child within.
2759 Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver.
2760*/
2761bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e)
2762{
2763 Q_D(QQuickFlickable);
2764 QPointerEvent *pointerEvent = e->isPointerEvent() ? static_cast<QPointerEvent *>(e) : nullptr;
2765
2766 auto wantsPointerEvent_helper = [this, d, i, pointerEvent]() {
2767 Q_ASSERT(pointerEvent);
2768 QQuickDeliveryAgentPrivate::localizePointerEvent(ev: pointerEvent, dest: this);
2769 const bool wants = d->wantsPointerEvent(pointerEvent);
2770 // re-localize event back to \a i before returning
2771 QQuickDeliveryAgentPrivate::localizePointerEvent(ev: pointerEvent, dest: i);
2772 return wants;
2773 };
2774
2775 if (!isVisible() || !isEnabled() || !isInteractive() ||
2776 (pointerEvent && !wantsPointerEvent_helper())) {
2777 d->cancelInteraction();
2778 return QQuickItem::childMouseEventFilter(i, e);
2779 }
2780
2781 if (e->type() == QEvent::UngrabMouse) {
2782 Q_ASSERT(e->isSinglePointEvent());
2783 auto spe = static_cast<QSinglePointEvent *>(e);
2784 const QObject *grabber = spe->exclusiveGrabber(point: spe->points().first());
2785 qCDebug(lcFilter) << "filtering UngrabMouse" << spe->points().first() << "for" << i << "grabber is" << grabber;
2786 if (grabber != this)
2787 mouseUngrabEvent(); // A child has been ungrabbed
2788 } else if (pointerEvent) {
2789 return filterPointerEvent(receiver: i, event: pointerEvent);
2790 }
2791
2792 return QQuickItem::childMouseEventFilter(i, e);
2793}
2794
2795/*!
2796 \qmlproperty real QtQuick::Flickable::maximumFlickVelocity
2797 This property holds the maximum velocity that the user can flick the view in pixels/second.
2798
2799 The default value is platform dependent.
2800*/
2801qreal QQuickFlickable::maximumFlickVelocity() const
2802{
2803 Q_D(const QQuickFlickable);
2804 return d->maxVelocity;
2805}
2806
2807void QQuickFlickable::setMaximumFlickVelocity(qreal v)
2808{
2809 Q_D(QQuickFlickable);
2810 if (v == d->maxVelocity)
2811 return;
2812 d->maxVelocity = v;
2813 emit maximumFlickVelocityChanged();
2814}
2815
2816/*!
2817 \qmlproperty real QtQuick::Flickable::flickDeceleration
2818 This property holds the rate at which a flick will decelerate:
2819 the higher the number, the faster it slows down when the user stops
2820 flicking via touch. For example 0.0001 is nearly
2821 "frictionless", and 10000 feels quite "sticky".
2822
2823 The default value is platform dependent. Values of zero or less are not allowed.
2824*/
2825qreal QQuickFlickable::flickDeceleration() const
2826{
2827 Q_D(const QQuickFlickable);
2828 return d->deceleration;
2829}
2830
2831void QQuickFlickable::setFlickDeceleration(qreal deceleration)
2832{
2833 Q_D(QQuickFlickable);
2834 if (deceleration == d->deceleration)
2835 return;
2836 d->deceleration = qMax(a: 0.001, b: deceleration);
2837 emit flickDecelerationChanged();
2838}
2839
2840bool QQuickFlickable::isFlicking() const
2841{
2842 Q_D(const QQuickFlickable);
2843 return d->hData.flicking || d->vData.flicking;
2844}
2845
2846/*!
2847 \qmlproperty bool QtQuick::Flickable::flicking
2848 \qmlproperty bool QtQuick::Flickable::flickingHorizontally
2849 \qmlproperty bool QtQuick::Flickable::flickingVertically
2850
2851 These properties describe whether the view is currently moving horizontally,
2852 vertically or in either direction, due to the user flicking the view.
2853*/
2854bool QQuickFlickable::isFlickingHorizontally() const
2855{
2856 Q_D(const QQuickFlickable);
2857 return d->hData.flicking;
2858}
2859
2860bool QQuickFlickable::isFlickingVertically() const
2861{
2862 Q_D(const QQuickFlickable);
2863 return d->vData.flicking;
2864}
2865
2866/*!
2867 \qmlproperty bool QtQuick::Flickable::dragging
2868 \qmlproperty bool QtQuick::Flickable::draggingHorizontally
2869 \qmlproperty bool QtQuick::Flickable::draggingVertically
2870
2871 These properties describe whether the view is currently moving horizontally,
2872 vertically or in either direction, due to the user dragging the view.
2873*/
2874bool QQuickFlickable::isDragging() const
2875{
2876 Q_D(const QQuickFlickable);
2877 return d->hData.dragging || d->vData.dragging;
2878}
2879
2880bool QQuickFlickable::isDraggingHorizontally() const
2881{
2882 Q_D(const QQuickFlickable);
2883 return d->hData.dragging;
2884}
2885
2886bool QQuickFlickable::isDraggingVertically() const
2887{
2888 Q_D(const QQuickFlickable);
2889 return d->vData.dragging;
2890}
2891
2892void QQuickFlickablePrivate::draggingStarting()
2893{
2894 Q_Q(QQuickFlickable);
2895 bool wasDragging = hData.dragging || vData.dragging;
2896 if (hMoved && !hData.dragging) {
2897 hData.dragging = true;
2898 emit q->draggingHorizontallyChanged();
2899 }
2900 if (vMoved && !vData.dragging) {
2901 vData.dragging = true;
2902 emit q->draggingVerticallyChanged();
2903 }
2904 if (!wasDragging && (hData.dragging || vData.dragging)) {
2905 emit q->draggingChanged();
2906 emit q->dragStarted();
2907 }
2908}
2909
2910void QQuickFlickablePrivate::draggingEnding()
2911{
2912 Q_Q(QQuickFlickable);
2913 const bool wasDragging = hData.dragging || vData.dragging;
2914 if (hData.dragging) {
2915 hData.dragging = false;
2916 emit q->draggingHorizontallyChanged();
2917 }
2918 if (vData.dragging) {
2919 vData.dragging = false;
2920 emit q->draggingVerticallyChanged();
2921 }
2922 if (wasDragging) {
2923 if (!hData.dragging && !vData.dragging) {
2924 emit q->draggingChanged();
2925 emit q->dragEnded();
2926 }
2927 hData.inRebound = false;
2928 vData.inRebound = false;
2929 }
2930}
2931
2932bool QQuickFlickablePrivate::isViewMoving() const
2933{
2934 if (timeline.isActive()
2935 || (hData.transitionToBounds && hData.transitionToBounds->isActive())
2936 || (vData.transitionToBounds && vData.transitionToBounds->isActive()) ) {
2937 return true;
2938 }
2939 return false;
2940}
2941
2942/*!
2943 \qmlproperty int QtQuick::Flickable::pressDelay
2944
2945 This property holds the time to delay (ms) delivering a press to
2946 children of the Flickable. This can be useful where reacting
2947 to a press before a flicking action has undesirable effects.
2948
2949 If the flickable is dragged/flicked before the delay times out
2950 the press event will not be delivered. If the button is released
2951 within the timeout, both the press and release will be delivered.
2952
2953 Note that for nested Flickables with pressDelay set, the pressDelay of
2954 outer Flickables is overridden by the innermost Flickable. If the drag
2955 exceeds the platform drag threshold, the press event will be delivered
2956 regardless of this property.
2957
2958 \sa QStyleHints
2959*/
2960int QQuickFlickable::pressDelay() const
2961{
2962 Q_D(const QQuickFlickable);
2963 return d->pressDelay;
2964}
2965
2966void QQuickFlickable::setPressDelay(int delay)
2967{
2968 Q_D(QQuickFlickable);
2969 if (d->pressDelay == delay)
2970 return;
2971 d->pressDelay = delay;
2972 emit pressDelayChanged();
2973}
2974
2975/*!
2976 \qmlproperty bool QtQuick::Flickable::moving
2977 \qmlproperty bool QtQuick::Flickable::movingHorizontally
2978 \qmlproperty bool QtQuick::Flickable::movingVertically
2979
2980 These properties describe whether the view is currently moving horizontally,
2981 vertically or in either direction, due to the user either dragging or
2982 flicking the view.
2983*/
2984
2985bool QQuickFlickable::isMoving() const
2986{
2987 Q_D(const QQuickFlickable);
2988 return d->hData.moving || d->vData.moving;
2989}
2990
2991bool QQuickFlickable::isMovingHorizontally() const
2992{
2993 Q_D(const QQuickFlickable);
2994 return d->hData.moving;
2995}
2996
2997bool QQuickFlickable::isMovingVertically() const
2998{
2999 Q_D(const QQuickFlickable);
3000 return d->vData.moving;
3001}
3002
3003void QQuickFlickable::velocityTimelineCompleted()
3004{
3005 Q_D(QQuickFlickable);
3006 if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive())
3007 || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) {
3008 return;
3009 }
3010 // With subclasses such as GridView, velocityTimeline.completed is emitted repeatedly:
3011 // for example setting currentIndex results in a visual "flick" which the user
3012 // didn't initiate directly. We don't want to end movement repeatedly, and in
3013 // that case movementEnding will happen after the sequence of movements ends.
3014 if (d->vData.flicking)
3015 movementEnding();
3016 d->updateBeginningEnd();
3017}
3018
3019void QQuickFlickable::timelineCompleted()
3020{
3021 Q_D(QQuickFlickable);
3022 if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive())
3023 || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) {
3024 return;
3025 }
3026 movementEnding();
3027 d->updateBeginningEnd();
3028}
3029
3030void QQuickFlickable::movementStarting()
3031{
3032 Q_D(QQuickFlickable);
3033 bool wasMoving = d->hData.moving || d->vData.moving;
3034 if (d->hMoved && !d->hData.moving) {
3035 d->hData.moving = true;
3036 emit movingHorizontallyChanged();
3037 }
3038 if (d->vMoved && !d->vData.moving) {
3039 d->vData.moving = true;
3040 emit movingVerticallyChanged();
3041 }
3042
3043 if (!wasMoving && (d->hData.moving || d->vData.moving)) {
3044 emit movingChanged();
3045 emit movementStarted();
3046#if QT_CONFIG(accessibility)
3047 if (QAccessible::isActive()) {
3048 QAccessibleEvent ev(this, QAccessible::ScrollingStart);
3049 QAccessible::updateAccessibility(event: &ev);
3050 }
3051#endif
3052 }
3053}
3054
3055void QQuickFlickable::movementEnding()
3056{
3057 movementEnding(hMovementEnding: true, vMovementEnding: true);
3058}
3059
3060void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding)
3061{
3062 Q_D(QQuickFlickable);
3063
3064 // emit flicking signals
3065 const bool wasFlicking = d->hData.flicking || d->vData.flicking;
3066 if (hMovementEnding && d->hData.flicking) {
3067 d->hData.flicking = false;
3068 emit flickingHorizontallyChanged();
3069 }
3070 if (vMovementEnding && d->vData.flicking) {
3071 d->vData.flicking = false;
3072 emit flickingVerticallyChanged();
3073 }
3074 if (wasFlicking && (!d->hData.flicking || !d->vData.flicking)) {
3075 emit flickingChanged();
3076 emit flickEnded();
3077 } else if (d->hData.flickingWhenDragBegan || d->vData.flickingWhenDragBegan) {
3078 d->hData.flickingWhenDragBegan = !hMovementEnding;
3079 d->vData.flickingWhenDragBegan = !vMovementEnding;
3080 emit flickEnded();
3081 }
3082
3083 // emit moving signals
3084 bool wasMoving = isMoving();
3085 if (hMovementEnding && d->hData.moving
3086 && (!d->pressed && !d->stealMouse)) {
3087 d->hData.moving = false;
3088 d->hMoved = false;
3089 emit movingHorizontallyChanged();
3090 }
3091 if (vMovementEnding && d->vData.moving
3092 && (!d->pressed && !d->stealMouse)) {
3093 d->vData.moving = false;
3094 d->vMoved = false;
3095 emit movingVerticallyChanged();
3096 }
3097 if (wasMoving && !isMoving()) {
3098 emit movingChanged();
3099 emit movementEnded();
3100#if QT_CONFIG(accessibility)
3101 if (QAccessible::isActive()) {
3102 QAccessibleEvent ev(this, QAccessible::ScrollingEnd);
3103 QAccessible::updateAccessibility(event: &ev);
3104 }
3105#endif
3106 }
3107
3108 if (hMovementEnding) {
3109 d->hData.fixingUp = false;
3110 d->hData.smoothVelocity.setValue(0);
3111 d->hData.previousDragDelta = 0.0;
3112 }
3113 if (vMovementEnding) {
3114 d->vData.fixingUp = false;
3115 d->vData.smoothVelocity.setValue(0);
3116 d->vData.previousDragDelta = 0.0;
3117 }
3118}
3119
3120void QQuickFlickablePrivate::updateVelocity()
3121{
3122 Q_Q(QQuickFlickable);
3123 emit q->horizontalVelocityChanged();
3124 emit q->verticalVelocityChanged();
3125}
3126
3127/*!
3128 \qmlproperty real QtQuick::Flickable::horizontalOvershoot
3129 \since 5.9
3130
3131 This property holds the horizontal overshoot, that is, the horizontal distance by
3132 which the contents has been dragged or flicked past the bounds of the flickable.
3133 The value is negative when the content is dragged or flicked beyond the beginning,
3134 and positive when beyond the end; \c 0.0 otherwise.
3135
3136 Whether the values are reported for dragging and/or flicking is determined by
3137 \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement
3138 is \c Flickable.StopAtBounds.
3139
3140 \sa verticalOvershoot, boundsBehavior, boundsMovement
3141*/
3142qreal QQuickFlickable::horizontalOvershoot() const
3143{
3144 Q_D(const QQuickFlickable);
3145 return d->hData.overshoot;
3146}
3147
3148/*!
3149 \qmlproperty real QtQuick::Flickable::verticalOvershoot
3150 \since 5.9
3151
3152 This property holds the vertical overshoot, that is, the vertical distance by
3153 which the contents has been dragged or flicked past the bounds of the flickable.
3154 The value is negative when the content is dragged or flicked beyond the beginning,
3155 and positive when beyond the end; \c 0.0 otherwise.
3156
3157 Whether the values are reported for dragging and/or flicking is determined by
3158 \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement
3159 is \c Flickable.StopAtBounds.
3160
3161 \sa horizontalOvershoot, boundsBehavior, boundsMovement
3162*/
3163qreal QQuickFlickable::verticalOvershoot() const
3164{
3165 Q_D(const QQuickFlickable);
3166 return d->vData.overshoot;
3167}
3168
3169/*!
3170 \qmlproperty enumeration QtQuick::Flickable::boundsMovement
3171 \since 5.10
3172
3173 This property holds whether the flickable will give a feeling that the edges of the
3174 view are soft, rather than a hard physical boundary.
3175
3176 The \c boundsMovement can be one of:
3177
3178 \list
3179 \li Flickable.StopAtBounds - this allows implementing custom edge effects where the
3180 contents do not follow drags or flicks beyond the bounds of the flickable. The values
3181 of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom
3182 edge effects.
3183 \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or
3184 flicks beyond the bounds of the flickable is determined by \l boundsBehavior.
3185 \endlist
3186
3187 The following example keeps the contents within bounds and instead applies a flip
3188 effect when flicked over horizontal bounds:
3189 \code
3190 Flickable {
3191 id: flickable
3192 boundsMovement: Flickable.StopAtBounds
3193 boundsBehavior: Flickable.DragAndOvershootBounds
3194 transform: Rotation {
3195 axis { x: 0; y: 1; z: 0 }
3196 origin.x: flickable.width / 2
3197 origin.y: flickable.height / 2
3198 angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot))
3199 }
3200 }
3201 \endcode
3202
3203 The following example keeps the contents within bounds and instead applies an opacity
3204 effect when dragged over vertical bounds:
3205 \code
3206 Flickable {
3207 boundsMovement: Flickable.StopAtBounds
3208 boundsBehavior: Flickable.DragOverBounds
3209 opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height)
3210 }
3211 \endcode
3212
3213 \sa boundsBehavior, verticalOvershoot, horizontalOvershoot
3214*/
3215QQuickFlickable::BoundsMovement QQuickFlickable::boundsMovement() const
3216{
3217 Q_D(const QQuickFlickable);
3218 return d->boundsMovement;
3219}
3220
3221void QQuickFlickable::setBoundsMovement(BoundsMovement movement)
3222{
3223 Q_D(QQuickFlickable);
3224 if (d->boundsMovement == movement)
3225 return;
3226
3227 d->boundsMovement = movement;
3228 emit boundsMovementChanged();
3229}
3230
3231QT_END_NAMESPACE
3232
3233#include "moc_qquickflickable_p_p.cpp"
3234
3235#include "moc_qquickflickable_p.cpp"
3236

source code of qtdeclarative/src/quick/items/qquickflickable.cpp