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

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