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