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 , stealMouse(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 stealMouse = true; // If we've been flicked then steal the click.
1131 int flickTime = timeline.time();
1132 if (flickTime > 600) {
1133 // too long between flicks - cancel boost
1134 hData.continuousFlickVelocity = 0;
1135 vData.continuousFlickVelocity = 0;
1136 flickBoost = 1.0;
1137 } else {
1138 hData.continuousFlickVelocity = -hData.smoothVelocity.value();
1139 vData.continuousFlickVelocity = -vData.smoothVelocity.value();
1140 if (flickTime > 300) // slower flicking - reduce boost
1141 flickBoost = qMax(1.0, flickBoost - 0.5);
1142 }
1143 } else {
1144 stealMouse = false;
1145 hData.continuousFlickVelocity = 0;
1146 vData.continuousFlickVelocity = 0;
1147 flickBoost = 1.0;
1148 }
1149 q->setKeepMouseGrab(stealMouse);
1150
1151 maybeBeginDrag(computeCurrentTime(event), event->points().first().position(),
1152 event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->buttons()
1153 : Qt::NoButton);
1154}
1155
1156void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn, Qt::MouseButtons buttons)
1157{
1158 Q_Q(QQuickFlickable);
1159 clearDelayedPress();
1160 // consider dragging only when buttons intersect acceptedButtons, or it's a touch event which has no button
1161 pressed = (buttons == Qt::NoButton) || (acceptedButtons != Qt::NoButton && (buttons & acceptedButtons) != 0);
1162
1163 if (hData.transitionToBounds)
1164 hData.transitionToBounds->stopTransition();
1165 if (vData.transitionToBounds)
1166 vData.transitionToBounds->stopTransition();
1167 if (!hData.fixingUp)
1168 resetTimeline(hData);
1169 if (!vData.fixingUp)
1170 resetTimeline(vData);
1171
1172 hData.reset();
1173 vData.reset();
1174 hData.dragMinBound = q->minXExtent() - hData.startMargin;
1175 vData.dragMinBound = q->minYExtent() - vData.startMargin;
1176 hData.dragMaxBound = q->maxXExtent() + hData.endMargin;
1177 vData.dragMaxBound = q->maxYExtent() + vData.endMargin;
1178 fixupMode = Normal;
1179 lastPos = QPointF();
1180 pressPos = pressPosn;
1181 hData.pressPos = hData.move.value();
1182 vData.pressPos = vData.move.value();
1183 const bool wasFlicking = hData.flicking || vData.flicking;
1184 hData.flickingWhenDragBegan = hData.flicking;
1185 vData.flickingWhenDragBegan = vData.flicking;
1186 if (hData.flicking) {
1187 hData.flicking = false;
1188 emit q->flickingHorizontallyChanged();
1189 }
1190 if (vData.flicking) {
1191 vData.flicking = false;
1192 emit q->flickingVerticallyChanged();
1193 }
1194 if (wasFlicking)
1195 emit q->flickingChanged();
1196 lastPosTime = lastPressTime = currentTimestamp;
1197 vData.velocityTime.start();
1198 hData.velocityTime.start();
1199}
1200
1201void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos,
1202 const QVector2D &deltas, bool overThreshold, bool momentum,
1203 bool velocitySensitiveOverBounds, const QVector2D &velocity)
1204{
1205 Q_Q(QQuickFlickable);
1206 bool rejectY = false;
1207 bool rejectX = false;
1208
1209 bool keepY = q->yflick();
1210 bool keepX = q->xflick();
1211
1212 bool stealY = false;
1213 bool stealX = false;
1214 if (eventType == QEvent::MouseMove) {
1215 stealX = stealY = stealMouse;
1216 } else if (eventType == QEvent::Wheel) {
1217 stealX = stealY = scrollingPhase;
1218 }
1219
1220 bool prevHMoved = hMoved;
1221 bool prevVMoved = vMoved;
1222
1223 qint64 elapsedSincePress = currentTimestamp - lastPressTime;
1224 qCDebug(lcFlickable).nospace() << currentTimestamp << ' ' << eventType << " drag @ " << localPos.x() << ',' << localPos.y()
1225 << " \u0394 " << deltas.x() << ',' << deltas.y() << " vel " << velocity.x() << ',' << velocity.y()
1226 << " thrsld? " << overThreshold << " momentum? " << momentum << " velSens? " << velocitySensitiveOverBounds
1227 << " sincePress " << elapsedSincePress;
1228
1229 if (q->yflick()) {
1230 qreal dy = deltas.y();
1231 if (overThreshold || elapsedSincePress > 200) {
1232 if (!vMoved && !vData.dragging)
1233 vData.dragStartOffset = dy;
1234 qreal newY = dy + vData.pressPos - (syncDrag ? 0 : vData.dragStartOffset);
1235 // Recalculate bounds in case margins have changed, but use the content
1236 // size estimate taken at the start of the drag in case the drag causes
1237 // the estimate to be altered
1238 const qreal minY = vData.dragMinBound + vData.startMargin;
1239 const qreal maxY = vData.dragMaxBound - vData.endMargin;
1240 if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) {
1241 if (fuzzyLessThanOrEqualTo(newY, maxY)) {
1242 newY = maxY;
1243 rejectY = vData.pressPos == maxY && vData.move.value() == maxY && dy < 0;
1244 }
1245 if (fuzzyLessThanOrEqualTo(minY, newY)) {
1246 newY = minY;
1247 rejectY |= vData.pressPos == minY && vData.move.value() == minY && dy > 0;
1248 }
1249 } else {
1250 qreal vel = velocity.y() / QML_FLICK_OVERSHOOTFRICTION;
1251 if (vel > 0. && vel > vData.velocity)
1252 vData.velocity = qMin(velocity.y() / QML_FLICK_OVERSHOOTFRICTION, maxVelocity);
1253 else if (vel < 0. && vel < vData.velocity)
1254 vData.velocity = qMax(velocity.y() / QML_FLICK_OVERSHOOTFRICTION, -maxVelocity);
1255 if (newY > minY) {
1256 // Overshoot beyond the top. But don't wait for momentum phase to end before returning to bounds.
1257 if (momentum && vData.atBeginning) {
1258 if (!vData.inRebound) {
1259 vData.inRebound = true;
1260 q->returnToBounds();
1261 }
1262 return;
1263 }
1264 if (velocitySensitiveOverBounds) {
1265 qreal overshoot = (newY - minY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1266 overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio());
1267 newY = minY + overshoot;
1268 } else {
1269 newY = minY + (newY - minY) / 2;
1270 }
1271 } else if (newY < maxY && maxY - minY <= 0) {
1272 // Overshoot beyond the bottom. But don't wait for momentum phase to end before returning to bounds.
1273 if (momentum && vData.atEnd) {
1274 if (!vData.inRebound) {
1275 vData.inRebound = true;
1276 q->returnToBounds();
1277 }
1278 return;
1279 }
1280 if (velocitySensitiveOverBounds) {
1281 qreal overshoot = (newY - maxY) * vData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1282 overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio());
1283 newY = maxY - overshoot;
1284 } else {
1285 newY = maxY + (newY - maxY) / 2;
1286 }
1287 }
1288 }
1289 if (!rejectY && stealMouse && dy != vData.previousDragDelta) {
1290 clearTimeline();
1291 vData.move.setValue(newY);
1292 vMoved = true;
1293 }
1294 if (!rejectY && overThreshold)
1295 stealY = true;
1296
1297 if ((newY >= minY && vData.pressPos == minY && vData.move.value() == minY && dy > 0)
1298 || (newY <= maxY && vData.pressPos == maxY && vData.move.value() == maxY && dy < 0)) {
1299 keepY = false;
1300 }
1301 }
1302 vData.previousDragDelta = dy;
1303 }
1304
1305 if (q->xflick()) {
1306 qreal dx = deltas.x();
1307 if (overThreshold || elapsedSincePress > 200) {
1308 if (!hMoved && !hData.dragging)
1309 hData.dragStartOffset = dx;
1310 qreal newX = dx + hData.pressPos - (syncDrag ? 0 : hData.dragStartOffset);
1311 const qreal minX = hData.dragMinBound + hData.startMargin;
1312 const qreal maxX = hData.dragMaxBound - hData.endMargin;
1313 if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) {
1314 if (fuzzyLessThanOrEqualTo(newX, maxX)) {
1315 newX = maxX;
1316 rejectX = hData.pressPos == maxX && hData.move.value() == maxX && dx < 0;
1317 }
1318 if (fuzzyLessThanOrEqualTo(minX, newX)) {
1319 newX = minX;
1320 rejectX |= hData.pressPos == minX && hData.move.value() == minX && dx > 0;
1321 }
1322 } else {
1323 qreal vel = velocity.x() / QML_FLICK_OVERSHOOTFRICTION;
1324 if (vel > 0. && vel > hData.velocity)
1325 hData.velocity = qMin(velocity.x() / QML_FLICK_OVERSHOOTFRICTION, maxVelocity);
1326 else if (vel < 0. && vel < hData.velocity)
1327 hData.velocity = qMax(velocity.x() / QML_FLICK_OVERSHOOTFRICTION, -maxVelocity);
1328 if (newX > minX) {
1329 // Overshoot beyond the left. But don't wait for momentum phase to end before returning to bounds.
1330 if (momentum && hData.atBeginning) {
1331 if (!hData.inRebound) {
1332 hData.inRebound = true;
1333 q->returnToBounds();
1334 }
1335 return;
1336 }
1337 if (velocitySensitiveOverBounds) {
1338 qreal overshoot = (newX - minX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1339 overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio());
1340 newX = minX + overshoot;
1341 } else {
1342 newX = minX + (newX - minX) / 2;
1343 }
1344 } else if (newX < maxX && maxX - minX <= 0) {
1345 // Overshoot beyond the right. But don't wait for momentum phase to end before returning to bounds.
1346 if (momentum && hData.atEnd) {
1347 if (!hData.inRebound) {
1348 hData.inRebound = true;
1349 q->returnToBounds();
1350 }
1351 return;
1352 }
1353 if (velocitySensitiveOverBounds) {
1354 qreal overshoot = (newX - maxX) * hData.velocity / maxVelocity / QML_FLICK_OVERSHOOTFRICTION;
1355 overshoot = QML_FLICK_OVERSHOOT * effectiveDevicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / effectiveDevicePixelRatio());
1356 newX = maxX - overshoot;
1357 } else {
1358 newX = maxX + (newX - maxX) / 2;
1359 }
1360 }
1361 }
1362 if (!rejectX && stealMouse && dx != hData.previousDragDelta) {
1363 clearTimeline();
1364 hData.move.setValue(newX);
1365 hMoved = true;
1366 }
1367
1368 if (!rejectX && overThreshold)
1369 stealX = true;
1370
1371 if ((newX >= minX && vData.pressPos == minX && vData.move.value() == minX && dx > 0)
1372 || (newX <= maxX && vData.pressPos == maxX && vData.move.value() == maxX && dx < 0)) {
1373 keepX = false;
1374 }
1375 }
1376 hData.previousDragDelta = dx;
1377 }
1378
1379 stealMouse = stealX || stealY;
1380 if (stealMouse) {
1381 if ((stealX && keepX) || (stealY && keepY))
1382 q->setKeepMouseGrab(true);
1383 clearDelayedPress();
1384 }
1385
1386 if (rejectY) {
1387 vData.velocityBuffer.clear();
1388 vData.velocity = 0;
1389 }
1390 if (rejectX) {
1391 hData.velocityBuffer.clear();
1392 hData.velocity = 0;
1393 }
1394
1395 if (momentum && !hData.flicking && !vData.flicking)
1396 flickingStarted(hData.velocity != 0, vData.velocity != 0);
1397 draggingStarting();
1398
1399 if ((hMoved && !prevHMoved) || (vMoved && !prevVMoved))
1400 q->movementStarting();
1401
1402 lastPosTime = currentTimestamp;
1403 if (q->yflick() && !rejectY)
1404 vData.addVelocitySample(velocity.y(), maxVelocity);
1405 if (q->xflick() && !rejectX)
1406 hData.addVelocitySample(velocity.x(), maxVelocity);
1407 lastPos = localPos;
1408}
1409
1410void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event)
1411{
1412 Q_Q(QQuickFlickable);
1413 if (!interactive || lastPosTime == -1 ||
1414 (event->isSinglePointEvent() && !buttonsAccepted(static_cast<QSinglePointEvent *>(event))))
1415 return;
1416
1417 qint64 currentTimestamp = computeCurrentTime(event);
1418 const auto &firstPoint = event->points().first();
1419 const auto &pos = firstPoint.position();
1420 const QVector2D deltas = QVector2D(pos - q->mapFromGlobal(firstPoint.globalPressPosition()));
1421 const QVector2D velocity = firstPointLocalVelocity(event);
1422 bool overThreshold = false;
1423
1424 if (q->isMoving()) {
1425 /*
1426 Only the first drag should be used to determine if the Flickable should start moving,
1427 to the exclusion of some inner Control (such as Slider) or a child Flickable.
1428 If the user releases the mouse or finger and drags again, this Flickable is the only
1429 sensible recipient as long as it's still moving.
1430 We also only care about the drag threshold for the first drag. If it's already moving,
1431 every subsequent move event (however small) should move the content item immediately.
1432 */
1433 overThreshold = true;
1434 } else if (event->pointCount() == 1) {
1435 if (q->yflick())
1436 overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint);
1437 if (q->xflick())
1438 overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint);
1439 } else {
1440 qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event;
1441 }
1442
1443 drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity);
1444}
1445
1446void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event)
1447{
1448 Q_Q(QQuickFlickable);
1449 stealMouse = false;
1450 q->setKeepMouseGrab(false);
1451 pressed = false;
1452
1453 // if we drag then pause before release we should not cause a flick.
1454 qint64 elapsed = computeCurrentTime(event) - lastPosTime;
1455
1456 vData.updateVelocity();
1457 hData.updateVelocity();
1458
1459 draggingEnding();
1460
1461 if (lastPosTime == -1)
1462 return;
1463
1464 hData.vTime = vData.vTime = timeline.time();
1465
1466 bool canBoost = false;
1467 const auto pos = event->points().first().position();
1468 const auto pressPos = q->mapFromGlobal(event->points().first().globalPressPosition());
1469 const QVector2D eventVelocity = firstPointLocalVelocity(event);
1470 qCDebug(lcVel) << event->deviceType() << event->type() << "velocity" << event->points().first().velocity() << "transformed to local" << eventVelocity;
1471
1472 qreal vVelocity = 0;
1473 if (elapsed < 100 && vData.velocity != 0.) {
1474 vVelocity = (event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity)
1475 ? eventVelocity.y() : vData.velocity);
1476 }
1477 if ((vData.atBeginning && vVelocity > 0.) || (vData.atEnd && vVelocity < 0.)) {
1478 vVelocity /= 2;
1479 } else if (vData.continuousFlickVelocity != 0.0
1480 && vData.viewSize/q->height() > QML_FLICK_MULTIFLICK_RATIO
1481 && ((vVelocity > 0) == (vData.continuousFlickVelocity > 0))
1482 && qAbs(vVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) {
1483 // accelerate flick for large view flicked quickly
1484 canBoost = true;
1485 }
1486
1487 qreal hVelocity = 0;
1488 if (elapsed < 100 && hData.velocity != 0.) {
1489 hVelocity = (event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity)
1490 ? eventVelocity.x() : hData.velocity);
1491 }
1492 if ((hData.atBeginning && hVelocity > 0.) || (hData.atEnd && hVelocity < 0.)) {
1493 hVelocity /= 2;
1494 } else if (hData.continuousFlickVelocity != 0.0
1495 && hData.viewSize/q->width() > QML_FLICK_MULTIFLICK_RATIO
1496 && ((hVelocity > 0) == (hData.continuousFlickVelocity > 0))
1497 && qAbs(hVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) {
1498 // accelerate flick for large view flicked quickly
1499 canBoost = true;
1500 }
1501
1502 flickBoost = canBoost ? qBound(1.0, flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0;
1503 const int flickThreshold = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickStartDistance).toInt();
1504
1505 bool anyPointGrabbed = event->points().constEnd() !=
1506 std::find_if(event->points().constBegin(),event->points().constEnd(),
1507 [q, event](const QEventPoint &point) { return event->exclusiveGrabber(point) == q; });
1508
1509 bool flickedVertically = false;
1510 vVelocity *= flickBoost;
1511 const bool isVerticalFlickAllowed = anyPointGrabbed &&
1512 q->yflick() && qAbs(vVelocity) > _q_MinimumFlickVelocity &&
1513 qAbs(pos.y() - pressPos.y()) > flickThreshold;
1514 if (isVerticalFlickAllowed) {
1515 velocityTimeline.reset(vData.smoothVelocity);
1516 vData.smoothVelocity.setValue(-vVelocity);
1517 flickedVertically = flickY(event->type(), vVelocity);
1518 }
1519
1520 bool flickedHorizontally = false;
1521 hVelocity *= flickBoost;
1522 const bool isHorizontalFlickAllowed = anyPointGrabbed &&
1523 q->xflick() && qAbs(hVelocity) > _q_MinimumFlickVelocity &&
1524 qAbs(pos.x() - pressPos.x()) > flickThreshold;
1525 if (isHorizontalFlickAllowed) {
1526 velocityTimeline.reset(hData.smoothVelocity);
1527 hData.smoothVelocity.setValue(-hVelocity);
1528 flickedHorizontally = flickX(event->type(), hVelocity);
1529 }
1530
1531 if (!isVerticalFlickAllowed)
1532 fixupY();
1533
1534 if (!isHorizontalFlickAllowed)
1535 fixupX();
1536
1537 flickingStarted(flickedHorizontally, flickedVertically);
1538 if (!isViewMoving()) {
1539 q->movementEnding();
1540 } else {
1541 if (flickedVertically)
1542 vMoved = true;
1543 if (flickedHorizontally)
1544 hMoved = true;
1545 q->movementStarting();
1546 }
1547}
1548
1549bool QQuickFlickablePrivate::buttonsAccepted(const QSinglePointEvent *event)
1550{
1551 return !((event->button() & acceptedButtons) == 0 && (event->buttons() & acceptedButtons) == 0);
1552}
1553
1554void QQuickFlickable::mousePressEvent(QMouseEvent *event)
1555{
1556 Q_D(QQuickFlickable);
1557 if (d->interactive && !d->replayingPressEvent && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) {
1558 if (!d->pressed)
1559 d->handlePressEvent(event);
1560 event->accept();
1561 } else {
1562 QQuickItem::mousePressEvent(event);
1563 }
1564}
1565
1566void QQuickFlickable::mouseMoveEvent(QMouseEvent *event)
1567{
1568 Q_D(QQuickFlickable);
1569 if (d->interactive && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) {
1570 d->handleMoveEvent(event);
1571 event->accept();
1572 } else {
1573 QQuickItem::mouseMoveEvent(event);
1574 }
1575}
1576
1577void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
1578{
1579 Q_D(QQuickFlickable);
1580 if (d->interactive && d->buttonsAccepted(event) && d->wantsPointerEvent(event)) {
1581 if (d->delayedPressEvent) {
1582 d->replayDelayedPress();
1583
1584 auto &firstPoint = event->point(0);
1585 if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) {
1586 // Since we sent the delayed press to the window, we need to resend the release to the window too.
1587 // We're not copying or detaching, so restore the original event position afterwards.
1588 const auto oldPosition = firstPoint.position();
1589 QMutableEventPoint::setPosition(firstPoint, event->scenePosition());
1590 QCoreApplication::sendEvent(window(), event);
1591 QMutableEventPoint::setPosition(firstPoint, oldPosition);
1592 }
1593
1594 // And the event has been consumed
1595 d->stealMouse = false;
1596 d->pressed = false;
1597 return;
1598 }
1599
1600 d->handleReleaseEvent(event);
1601 event->accept();
1602 } else {
1603 QQuickItem::mouseReleaseEvent(event);
1604 }
1605}
1606
1607void QQuickFlickable::touchEvent(QTouchEvent *event)
1608{
1609 Q_D(QQuickFlickable);
1610
1611 if (event->type() == QEvent::TouchCancel) {
1612 if (d->interactive && d->wantsPointerEvent(event))
1613 d->cancelInteraction();
1614 else
1615 QQuickItem::touchEvent(event);
1616 return;
1617 }
1618
1619 bool unhandled = false;
1620 const auto &firstPoint = event->points().first();
1621 switch (firstPoint.state()) {
1622 case QEventPoint::State::Pressed:
1623 if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) {
1624 if (!d->pressed)
1625 d->handlePressEvent(event);
1626 event->accept();
1627 } else {
1628 unhandled = true;
1629 }
1630 break;
1631 case QEventPoint::State::Updated:
1632 if (d->interactive && d->wantsPointerEvent(event)) {
1633 d->handleMoveEvent(event);
1634 event->accept();
1635 } else {
1636 unhandled = true;
1637 }
1638 break;
1639 case QEventPoint::State::Released:
1640 if (d->interactive && d->wantsPointerEvent(event)) {
1641 if (d->delayedPressEvent) {
1642 d->replayDelayedPress();
1643
1644 const auto &firstPoint = event->point(0);
1645 if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) {
1646 // Since we sent the delayed press to the window, we need to resend the release to the window too.
1647 QScopedPointer<QPointerEvent> localizedEvent(
1648 QQuickDeliveryAgentPrivate::clonePointerEvent(event, firstPoint.scenePosition()));
1649 QCoreApplication::sendEvent(window(), localizedEvent.data());
1650 }
1651
1652 // And the event has been consumed
1653 d->stealMouse = false;
1654 d->pressed = false;
1655 return;
1656 }
1657
1658 d->handleReleaseEvent(event);
1659 event->accept();
1660 } else {
1661 unhandled = true;
1662 }
1663 break;
1664 case QEventPoint::State::Stationary:
1665 case QEventPoint::State::Unknown:
1666 break;
1667 }
1668 if (unhandled)
1669 QQuickItem::touchEvent(event);
1670}
1671
1672#if QT_CONFIG(wheelevent)
1673void QQuickFlickable::wheelEvent(QWheelEvent *event)
1674{
1675 Q_D(QQuickFlickable);
1676 if (!d->interactive || !d->wantsPointerEvent(event)) {
1677 QQuickItem::wheelEvent(event);
1678 return;
1679 }
1680 qCDebug(lcWheel) << event->device() << event << event->source();
1681 event->setAccepted(false);
1682 qint64 currentTimestamp = d->computeCurrentTime(event);
1683 switch (event->phase()) {
1684 case Qt::ScrollBegin:
1685 d->scrollingPhase = true;
1686 d->accumulatedWheelPixelDelta = QVector2D();
1687 d->vData.velocity = 0;
1688 d->hData.velocity = 0;
1689 d->timer.start();
1690 d->maybeBeginDrag(currentTimestamp, event->position());
1691 d->lastPosTime = -1;
1692 break;
1693 case Qt::NoScrollPhase: // default phase with an ordinary wheel mouse
1694 case Qt::ScrollUpdate:
1695 if (d->scrollingPhase)
1696 d->pressed = true;
1697 break;
1698 case Qt::ScrollMomentum:
1699 d->pressed = false;
1700 d->scrollingPhase = false;
1701 d->draggingEnding();
1702 if (isMoving())
1703 event->accept();
1704 d->lastPosTime = -1;
1705 break;
1706 case Qt::ScrollEnd:
1707 d->pressed = false;
1708 d->scrollingPhase = false;
1709 d->draggingEnding();
1710 returnToBounds();
1711 d->lastPosTime = -1;
1712 d->stealMouse = false;
1713 if (!d->velocityTimeline.isActive() && !d->timeline.isActive())
1714 movementEnding(true, true);
1715 return;
1716 }
1717
1718 qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / qreal(1000);
1719 if (elapsed <= 0) {
1720 d->lastPosTime = currentTimestamp;
1721 qCDebug(lcWheel) << "insufficient elapsed time: can't calculate velocity" << elapsed;
1722 return;
1723 }
1724
1725 if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull() || event->phase() == Qt::NoScrollPhase) {
1726 // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta
1727 int xDelta = event->angleDelta().x();
1728 int yDelta = event->angleDelta().y();
1729
1730 if (d->wheelDeceleration > _q_MaximumWheelDeceleration) {
1731 const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24;
1732 // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel
1733 // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines()
1734 if (yflick() && yDelta != 0) {
1735 d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
1736 d->vMoved = true;
1737 qreal scrollPixel = (-yDelta / 120.0 * wheelScroll);
1738 bool acceptEvent = true; // Set to false if event should propagate to parent
1739 if (scrollPixel > 0) { // Forward direction (away from user)
1740 if (d->vData.move.value() >= minYExtent()) {
1741 d->vMoved = false;
1742 acceptEvent = false;
1743 }
1744 } else { // Backward direction (towards user)
1745 if (d->vData.move.value() <= maxYExtent()) {
1746 d->vMoved = false;
1747 acceptEvent = false;
1748 }
1749 }
1750 if (d->vMoved) {
1751 if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
1752 const qreal estContentPos = scrollPixel + d->vData.move.value();
1753 if (scrollPixel > 0) { // Forward direction (away from user)
1754 if (estContentPos > minYExtent()) {
1755 scrollPixel = minYExtent() - d->vData.move.value();
1756 acceptEvent = false;
1757 }
1758 } else { // Backward direction (towards user)
1759 if (estContentPos < maxYExtent()) {
1760 scrollPixel = maxYExtent() - d->vData.move.value();
1761 acceptEvent = false;
1762 }
1763 }
1764 }
1765 d->resetTimeline(d->vData);
1766 movementStarting();
1767 d->timeline.moveBy(d->vData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4);
1768 d->vData.fixingUp = true;
1769 d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d));
1770 }
1771 if (acceptEvent)
1772 event->accept();
1773 }
1774 if (xflick() && xDelta != 0) {
1775 d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
1776 d->hMoved = true;
1777 qreal scrollPixel = (-xDelta / 120.0 * wheelScroll);
1778 bool acceptEvent = true; // Set to false if event should propagate to parent
1779 if (scrollPixel > 0) { // Forward direction (away from user)
1780 if (d->hData.move.value() >= minXExtent()) {
1781 d->hMoved = false;
1782 acceptEvent = false;
1783 }
1784 } else { // Backward direction (towards user)
1785 if (d->hData.move.value() <= maxXExtent()) {
1786 d->hMoved = false;
1787 acceptEvent = false;
1788 }
1789 }
1790 if (d->hMoved) {
1791 if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
1792 const qreal estContentPos = scrollPixel + d->hData.move.value();
1793 if (scrollPixel > 0) { // Forward direction (away from user)
1794 if (estContentPos > minXExtent()) {
1795 scrollPixel = minXExtent() - d->hData.move.value();
1796 acceptEvent = false;
1797 }
1798 } else { // Backward direction (towards user)
1799 if (estContentPos < maxXExtent()) {
1800 scrollPixel = maxXExtent() - d->hData.move.value();
1801 acceptEvent = false;
1802 }
1803 }
1804 }
1805 d->resetTimeline(d->hData);
1806 movementStarting();
1807 d->timeline.moveBy(d->hData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4);
1808 d->hData.fixingUp = true;
1809 d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d));
1810 }
1811 if (acceptEvent)
1812 event->accept();
1813 }
1814 } else {
1815 // wheelDeceleration is set to some reasonable value: the user or the platform wants to have
1816 // the classic Qt Quick mouse wheel acceleration behavior.
1817 // For a single "clicky" wheel event (angleDelta +/- 120),
1818 // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines().
1819 // The decel algo from there is
1820 // qreal dist = v2 / (accel * 2.0);
1821 // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2)
1822 // now solve for dt:
1823 // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance)
1824 if (!isMoving())
1825 elapsed = 120 / qSqrt(d->wheelDeceleration * 2 * d->initialWheelFlickDistance);
1826 if (yflick() && yDelta != 0) {
1827 qreal instVelocity = yDelta / elapsed;
1828 // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
1829 if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0))
1830 d->vData.velocityBuffer.clear();
1831 d->vData.addVelocitySample(instVelocity, d->maxVelocity);
1832 d->vData.updateVelocity();
1833 if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) {
1834 const bool newFlick = d->flickY(event->type(), d->vData.velocity);
1835 if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) {
1836 d->flickingStarted(false, true);
1837 d->vMoved = true;
1838 movementStarting();
1839 }
1840 event->accept();
1841 }
1842 }
1843 if (xflick() && xDelta != 0) {
1844 qreal instVelocity = xDelta / elapsed;
1845 // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
1846 if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0))
1847 d->hData.velocityBuffer.clear();
1848 d->hData.addVelocitySample(instVelocity, d->maxVelocity);
1849 d->hData.updateVelocity();
1850 if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) {
1851 const bool newFlick = d->flickX(event->type(), d->hData.velocity);
1852 if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) {
1853 d->flickingStarted(true, false);
1854 d->hMoved = true;
1855 movementStarting();
1856 }
1857 event->accept();
1858 }
1859 }
1860 }
1861 } else {
1862 // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually
1863 int xDelta = event->pixelDelta().x();
1864 int yDelta = event->pixelDelta().y();
1865
1866 QVector2D velocity(xDelta / elapsed, yDelta / elapsed);
1867 d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta());
1868 // Try to drag if 1) we already are dragging or flicking, or
1869 // 2) the flickable is free to flick both directions, or
1870 // 3) the movement so far has been mostly horizontal AND it's free to flick horizontally, or
1871 // 4) the movement so far has been mostly vertical AND it's free to flick vertically.
1872 // Otherwise, wait until the next event. Wheel events with pixel deltas tend to come frequently.
1873 if (isMoving() || isFlicking() || (yflick() && xflick())
1874 || (xflick() && qAbs(d->accumulatedWheelPixelDelta.x()) > qAbs(d->accumulatedWheelPixelDelta.y() * 2))
1875 || (yflick() && qAbs(d->accumulatedWheelPixelDelta.y()) > qAbs(d->accumulatedWheelPixelDelta.x() * 2))) {
1876 d->drag(currentTimestamp, event->type(), event->position(), d->accumulatedWheelPixelDelta,
1877 true, !d->scrollingPhase, true, velocity);
1878 d->updateBeginningEnd();
1879 if ((xflick() && !isAtXBeginning() && !isAtXEnd()) || (yflick() && !isAtYBeginning() && !isAtYEnd()))
1880 event->accept();
1881 } else {
1882 qCDebug(lcWheel) << "not dragging: accumulated deltas" << d->accumulatedWheelPixelDelta <<
1883 "moving?" << isMoving() << "can flick horizontally?" << xflick() << "vertically?" << yflick();
1884 }
1885 }
1886 d->lastPosTime = currentTimestamp;
1887
1888 if (!event->isAccepted())
1889 QQuickItem::wheelEvent(event);
1890}
1891#endif
1892
1893bool QQuickFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const
1894{
1895 Q_Q(const QQuickFlickable);
1896 QQuickItem *item = i;
1897 while (item) {
1898 QQuickFlickable *flick = qobject_cast<QQuickFlickable*>(item);
1899 if (flick && flick->pressDelay() > 0 && flick->isInteractive()) {
1900 // Found the innermost flickable with press delay - is it me?
1901 return (flick == q);
1902 }
1903 item = item->parentItem();
1904 }
1905 return false;
1906}
1907
1908void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent *event)
1909{
1910 Q_Q(QQuickFlickable);
1911 if (!q->window() || pressDelay <= 0)
1912 return;
1913
1914 // Only the innermost flickable should handle the delayed press; this allows
1915 // flickables up the parent chain to all see the events in their filter functions
1916 if (!isInnermostPressDelay(item))
1917 return;
1918
1919 delayedPressEvent = QQuickDeliveryAgentPrivate::clonePointerEvent(event);
1920 delayedPressEvent->setAccepted(false);
1921 delayedPressTimer.start(pressDelay, q);
1922 qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent;
1923}
1924
1925void QQuickFlickablePrivate::clearDelayedPress()
1926{
1927 if (delayedPressEvent) {
1928 delayedPressTimer.stop();
1929 qCDebug(lcReplay) << "clear delayed press" << delayedPressEvent;
1930 delete delayedPressEvent;
1931 delayedPressEvent = nullptr;
1932 }
1933}
1934
1935void QQuickFlickablePrivate::replayDelayedPress()
1936{
1937 Q_Q(QQuickFlickable);
1938 if (delayedPressEvent) {
1939 // Losing the grab will clear the delayed press event; take control of it here
1940 QScopedPointer<QPointerEvent> event(delayedPressEvent);
1941 delayedPressEvent = nullptr;
1942 delayedPressTimer.stop();
1943
1944 // If we have the grab, release before delivering the event
1945 if (QQuickWindow *window = q->window()) {
1946 auto da = deliveryAgentPrivate();
1947 da->allowChildEventFiltering = false; // don't allow re-filtering during replay
1948 replayingPressEvent = true;
1949 auto &firstPoint = event->point(0);
1950 // At first glance, it's weird for delayedPressEvent to already have a grabber;
1951 // but on press, filterMouseEvent() took the exclusive grab, and that's stored
1952 // in the device-specific EventPointData instance in QPointingDevicePrivate::activePoints,
1953 // not in the event itself. If this Flickable is still the grabber of that point on that device,
1954 // that's the reason; but now it doesn't need that grab anymore.
1955 if (event->exclusiveGrabber(firstPoint) == q)
1956 event->setExclusiveGrabber(firstPoint, nullptr);
1957
1958 qCDebug(lcReplay) << "replaying" << event.data();
1959 // Put scenePosition into position, for the sake of QQuickWindowPrivate::translateTouchEvent()
1960 // TODO remove this if we remove QQuickWindowPrivate::translateTouchEvent()
1961 QMutableEventPoint::setPosition(firstPoint, firstPoint.scenePosition());
1962 // Send it through like a fresh press event, and let QQuickWindow
1963 // (more specifically, QQuickWindowPrivate::deliverPressOrReleaseEvent)
1964 // find the item or handler that should receive it, as usual.
1965 QCoreApplication::sendEvent(window, event.data());
1966 qCDebug(lcReplay) << "replay done";
1967
1968 // We're done with replay, go back to normal delivery behavior
1969 replayingPressEvent = false;
1970 da->allowChildEventFiltering = true;
1971 }
1972 }
1973}
1974
1975//XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned.
1976
1977/*!
1978 \internal
1979
1980 This function is called from the timeline,
1981 when advancement in the timeline is modifying the hData.move value.
1982 The \a x argument is the newly updated value in hData.move.
1983 The purpose of the function is to update the x position of the contentItem.
1984*/
1985void QQuickFlickablePrivate::setViewportX(qreal x)
1986{
1987 Q_Q(QQuickFlickable);
1988 qreal effectiveX = pixelAligned ? -std::round(-x) : x;
1989
1990 const qreal maxX = q->maxXExtent();
1991 const qreal minX = q->minXExtent();
1992
1993 if (boundsMovement == int(QQuickFlickable::StopAtBounds))
1994 effectiveX = qBound(maxX, effectiveX, minX);
1995
1996 contentItem->setX(effectiveX);
1997 if (contentItem->x() != effectiveX)
1998 return; // reentered
1999
2000 qreal overshoot = 0.0;
2001 if (x <= maxX)
2002 overshoot = maxX - x;
2003 else if (x >= minX)
2004 overshoot = minX - x;
2005
2006 if (overshoot != hData.overshoot) {
2007 hData.overshoot = overshoot;
2008 emit q->horizontalOvershootChanged();
2009 }
2010}
2011
2012/*!
2013 \internal
2014
2015 This function is called from the timeline,
2016 when advancement in the timeline is modifying the vData.move value.
2017 The \a y argument is the newly updated value in vData.move.
2018 The purpose of the function is to update the y position of the contentItem.
2019*/
2020void QQuickFlickablePrivate::setViewportY(qreal y)
2021{
2022 Q_Q(QQuickFlickable);
2023 qreal effectiveY = pixelAligned ? -std::round(-y) : y;
2024
2025 const qreal maxY = q->maxYExtent();
2026 const qreal minY = q->minYExtent();
2027
2028 if (boundsMovement == int(QQuickFlickable::StopAtBounds))
2029 effectiveY = qBound(maxY, effectiveY, minY);
2030
2031 contentItem->setY(effectiveY);
2032 if (contentItem->y() != effectiveY)
2033 return; // reentered
2034
2035 qreal overshoot = 0.0;
2036 if (y <= maxY)
2037 overshoot = maxY - y;
2038 else if (y >= minY)
2039 overshoot = minY - y;
2040
2041 if (overshoot != vData.overshoot) {
2042 vData.overshoot = overshoot;
2043 emit q->verticalOvershootChanged();
2044 }
2045}
2046
2047void QQuickFlickable::timerEvent(QTimerEvent *event)
2048{
2049 Q_D(QQuickFlickable);
2050 if (event->timerId() == d->delayedPressTimer.timerId()) {
2051 d->delayedPressTimer.stop();
2052 if (d->delayedPressEvent) {
2053 d->replayDelayedPress();
2054 }
2055 }
2056}
2057
2058qreal QQuickFlickable::minYExtent() const
2059{
2060 Q_D(const QQuickFlickable);
2061 return d->vData.startMargin;
2062}
2063
2064qreal QQuickFlickable::minXExtent() const
2065{
2066 Q_D(const QQuickFlickable);
2067 return d->hData.startMargin;
2068}
2069
2070/* returns -ve */
2071qreal QQuickFlickable::maxXExtent() const
2072{
2073 Q_D(const QQuickFlickable);
2074 return qMin<qreal>(minXExtent(), width() - vWidth() - d->hData.endMargin);
2075}
2076/* returns -ve */
2077qreal QQuickFlickable::maxYExtent() const
2078{
2079 Q_D(const QQuickFlickable);
2080 return qMin<qreal>(minYExtent(), height() - vHeight() - d->vData.endMargin);
2081}
2082
2083void QQuickFlickable::componentComplete()
2084{
2085 Q_D(QQuickFlickable);
2086 QQuickItem::componentComplete();
2087 if (!d->hData.explicitValue && d->hData.startMargin != 0.)
2088 setContentX(-minXExtent());
2089 if (!d->vData.explicitValue && d->vData.startMargin != 0.)
2090 setContentY(-minYExtent());
2091 if (lcWheel().isDebugEnabled() || lcVel().isDebugEnabled()) {
2092 d->timeline.setObjectName(QLatin1String("timeline for Flickable ") + objectName());
2093 d->velocityTimeline.setObjectName(QLatin1String("velocity timeline for Flickable ") + objectName());
2094 }
2095}
2096
2097void QQuickFlickable::viewportMoved(Qt::Orientations orient)
2098{
2099 Q_D(QQuickFlickable);
2100 if (orient & Qt::Vertical)
2101 d->viewportAxisMoved(d->vData, minYExtent(), maxYExtent(), d->fixupY_callback);
2102 if (orient & Qt::Horizontal)
2103 d->viewportAxisMoved(d->hData, minXExtent(), maxXExtent(), d->fixupX_callback);
2104 d->updateBeginningEnd();
2105}
2106
2107void QQuickFlickablePrivate::viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent,
2108 QQuickTimeLineCallback::Callback fixupCallback)
2109{
2110 if (!scrollingPhase && (pressed || calcVelocity)) {
2111 int elapsed = data.velocityTime.restart();
2112 if (elapsed > 0) {
2113 qreal velocity = (data.lastPos - data.move.value()) * 1000 / elapsed;
2114 if (qAbs(velocity) > 0) {
2115 velocityTimeline.reset(data.smoothVelocity);
2116 velocityTimeline.set(data.smoothVelocity, velocity);
2117 qCDebug(lcVel) << "touchpad scroll phase: velocity" << velocity;
2118 }
2119 }
2120 } else {
2121 if (timeline.time() > data.vTime) {
2122 velocityTimeline.reset(data.smoothVelocity);
2123 int dt = timeline.time() - data.vTime;
2124 if (dt > 2) {
2125 qreal velocity = (data.lastPos - data.move.value()) * 1000 / dt;
2126 if (!qFuzzyCompare(data.smoothVelocity.value(), velocity))
2127 qCDebug(lcVel) << "velocity" << data.smoothVelocity.value() << "->" << velocity
2128 << "computed as (" << data.lastPos << "-" << data.move.value() << ") * 1000 / ("
2129 << timeline.time() << "-" << data.vTime << ")";
2130 data.smoothVelocity.setValue(velocity);
2131 }
2132 }
2133 }
2134
2135 if (!data.inOvershoot && !data.fixingUp && data.flicking
2136 && (data.move.value() > minExtent || data.move.value() < maxExtent)
2137 && qAbs(data.smoothVelocity.value()) > 10) {
2138 // Increase deceleration if we've passed a bound
2139 qreal overBound = data.move.value() > minExtent
2140 ? data.move.value() - minExtent
2141 : maxExtent - data.move.value();
2142 data.inOvershoot = true;
2143 qreal maxDistance = overShootDistance(qAbs(data.smoothVelocity.value())) - overBound;
2144 resetTimeline(data);
2145 if (maxDistance > 0)
2146 timeline.accel(data.move, -data.smoothVelocity.value(), deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
2147 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
2148 }
2149
2150 data.lastPos = data.move.value();
2151 data.vTime = timeline.time();
2152}
2153
2154void QQuickFlickable::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2155{
2156 Q_D(QQuickFlickable);
2157 QQuickItem::geometryChange(newGeometry, oldGeometry);
2158
2159 bool changed = false;
2160 if (newGeometry.width() != oldGeometry.width()) {
2161 changed = true; // we must update visualArea.widthRatio
2162 if (d->hData.viewSize < 0)
2163 d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin);
2164 // Make sure that we're entirely in view.
2165 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2166 d->fixupMode = QQuickFlickablePrivate::Immediate;
2167 d->fixupX();
2168 }
2169 }
2170 if (newGeometry.height() != oldGeometry.height()) {
2171 changed = true; // we must update visualArea.heightRatio
2172 if (d->vData.viewSize < 0)
2173 d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin);
2174 // Make sure that we're entirely in view.
2175 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2176 d->fixupMode = QQuickFlickablePrivate::Immediate;
2177 d->fixupY();
2178 }
2179 }
2180
2181 if (changed)
2182 d->updateBeginningEnd();
2183}
2184
2185/*!
2186 \qmlmethod QtQuick::Flickable::flick(qreal xVelocity, qreal yVelocity)
2187
2188 Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec.
2189
2190 Calling this method will update the corresponding moving and flicking properties and signals,
2191 just like a real touchscreen flick.
2192*/
2193
2194void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity)
2195{
2196 Q_D(QQuickFlickable);
2197 d->hData.reset();
2198 d->vData.reset();
2199 d->hData.velocity = xVelocity;
2200 d->vData.velocity = yVelocity;
2201 d->hData.vTime = d->vData.vTime = d->timeline.time();
2202
2203 const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(QEvent::TouchUpdate, xVelocity);
2204 const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(QEvent::TouchUpdate, yVelocity);
2205
2206 if (flickedX)
2207 d->hMoved = true;
2208 if (flickedY)
2209 d->vMoved = true;
2210 movementStarting();
2211 d->flickingStarted(flickedX, flickedY);
2212}
2213
2214void QQuickFlickablePrivate::flickingStarted(bool flickingH, bool flickingV)
2215{
2216 Q_Q(QQuickFlickable);
2217 if (!flickingH && !flickingV)
2218 return;
2219
2220 bool wasFlicking = hData.flicking || vData.flicking;
2221 if (flickingH && !hData.flicking) {
2222 hData.flicking = true;
2223 emit q->flickingHorizontallyChanged();
2224 }
2225 if (flickingV && !vData.flicking) {
2226 vData.flicking = true;
2227 emit q->flickingVerticallyChanged();
2228 }
2229 if (!wasFlicking && (hData.flicking || vData.flicking)) {
2230 emit q->flickingChanged();
2231 emit q->flickStarted();
2232 }
2233}
2234
2235/*!
2236 \qmlmethod QtQuick::Flickable::cancelFlick()
2237
2238 Cancels the current flick animation.
2239*/
2240
2241void QQuickFlickable::cancelFlick()
2242{
2243 Q_D(QQuickFlickable);
2244 d->resetTimeline(d->hData);
2245 d->resetTimeline(d->vData);
2246 movementEnding();
2247}
2248
2249/*!
2250 \qmlmethod QtQuick::Flickable::positionViewAtChild(QQuickItem *child, PositionMode mode, point offset)
2251 \since 6.11
2252
2253 Positions \l {Flickable::}{contentX} and \l {Flickable::}{contentY} such
2254 that \a child item (if it is a child) is at the position specified by \a mode. \a mode
2255 can be an or-ed combination of the following:
2256
2257 \value Flickable.AlignLeft Position the child at the left of the view.
2258 \value Flickable.AlignHCenter Position the child at the horizontal center of the view.
2259 \value Flickable.AlignRight Position the child at the right of the view.
2260 \value Flickable.AlignTop Position the child at the top of the view.
2261 \value Flickable.AlignVCenter Position the child at the vertical center of the view.
2262 \value Flickable.AlignBottom Position the child at the bottom of the view.
2263 \value Flickable.AlignCenter The same as (Flickable.AlignHCenter | Flickable.AlignVCenter)
2264 \value Flickable.Visible If any part of the child is visible then take no action. Otherwise
2265 move the content item so that the entire child becomes visible.
2266 \value Flickable.Contain If the entire child is visible then take no action. Otherwise
2267 move the content item so that the entire child becomes visible. If the child is
2268 bigger than the view, the top-left part of the child will be preferred.
2269
2270 If no vertical alignment is specified, vertical positioning will be ignored.
2271 The same is true for horizontal alignment.
2272
2273 Optionally, you can specify \a offset to move \e contentX and \e contentY an extra number of
2274 pixels beyond the target alignment.
2275
2276 If positioning the flickable at the child item would cause empty space to be displayed at the
2277 beginning or end of the flickable, the flickable will be positioned at the boundary.
2278
2279 \snippet qml/flickablePositionActiveFocusPosition.qml 0
2280*/
2281
2282void QQuickFlickable::positionViewAtChild(QQuickItem *child, PositionMode mode, const QPointF &offset)
2283{
2284 Q_D(QQuickFlickable);
2285 cancelFlick();
2286
2287 if (!d->contentItem->isAncestorOf(child))
2288 return;
2289
2290 const QRectF itemRect =
2291 child->mapRectToItem(d->contentItem, QRectF(0, 0, child->width(), child->height()));
2292
2293 QPointF currentPosition = QPointF(contentX(), contentY());
2294 QPointF newPosition = computePosition(currentPosition, itemRect, mode, offset);
2295
2296 if (newPosition.x() != currentPosition.x()) {
2297 setContentX(newPosition.x());
2298 d->fixupX();
2299 }
2300 if (newPosition.y() != currentPosition.y()) {
2301 setContentY(newPosition.y());
2302 d->fixupY();
2303 }
2304}
2305
2306/*!
2307 \qmlmethod QtQuick::Flickable::flickToChild(QQuickItem *child, PositionMode mode, point offset)
2308 \since 6.11
2309
2310 Flicks the flickable such that \a child item (if it is a child) is at the position
2311 specified by \a mode. \a mode can be an or-ed combination of the following:
2312
2313 \value Flickable.AlignLeft Flick the child at the left of the view.
2314 \value Flickable.AlignHCenter Flick the child at the horizontal center of the view.
2315 \value Flickable.AlignRight Flick the child at the right of the view.
2316 \value Flickable.AlignTop Flick the child at the top of the view.
2317 \value Flickable.AlignVCenter Flick the child at the vertical center of the view.
2318 \value Flickable.AlignBottom Flick the child at the bottom of the view.
2319 \value Flickable.AlignCenter The same as (Flickable.AlignHCenter | Flickable.AlignVCenter)
2320 \value Flickable.Visible If any part of the child is visible then take no action. Otherwise
2321 move the content item so that the entire child becomes visible.
2322 \value Flickable.Contain If the entire child is visible then take no action. Otherwise
2323 move the content item so that the entire child becomes visible. If the child is
2324 bigger than the view, the top-left part of the child will be preferred.
2325
2326 If no vertical alignment is specified, vertical flicking will be ignored.
2327 The same is true for horizontal alignment.
2328
2329 Optionally, you can specify \a offset to flick an extra number of
2330 pixels beyond the target alignment.
2331
2332 If flicking the flickable at the child item would cause empty space to be displayed at the
2333 beginning or end of the flickable, the flickable will stop flicking at the boundary.
2334
2335 \snippet qml/flickableFlickActiveFocusPosition.qml 0
2336*/
2337
2338void QQuickFlickable::flickToChild(QQuickItem *child, PositionMode mode, const QPointF &offset)
2339{
2340 Q_D(QQuickFlickable);
2341 cancelFlick();
2342
2343 if (!d->contentItem->isAncestorOf(child))
2344 return;
2345
2346 const QRectF itemRect =
2347 child->mapRectToItem(d->contentItem, QRectF(0, 0, child->width(), child->height()));
2348
2349 QPointF currentPosition = QPointF(contentX(), contentY());
2350 QPointF newPosition = computePosition(currentPosition, itemRect, mode, offset);
2351
2352 flickTo(newPosition);
2353}
2354
2355/*!
2356 \qmlmethod QtQuick::Flickable::flickTo(point position)
2357 \since 6.11
2358
2359 Flicks the flickable to \a position.
2360
2361 If flicking the flickable would cause empty space to be displayed at the
2362 beginning or end of the flickable, the flickable will stop flicking at the boundary.
2363*/
2364
2365void QQuickFlickable::flickTo(const QPointF &newPosition)
2366{
2367 Q_D(QQuickFlickable);
2368
2369 QPointF currentPosition = QPointF(contentX(), contentY());
2370
2371 qreal xVelocity = 0.0;
2372 qreal yVelocity = 0.0;
2373
2374 const qreal deltaX = newPosition.x() - currentPosition.x();
2375 const qreal deltaY = newPosition.y() - currentPosition.y();
2376
2377 // Calculate velocity based on distance to travel
2378 // Formula: v = sqrt(2 * deceleration * distance)
2379
2380 if (xflick() && qAbs(deltaX) > 0.5) {
2381 const qreal decel = flickDeceleration();
2382 qreal velocity = qSqrt(2.0 * decel * qAbs(deltaX));
2383 if (qAbs(velocity) < _q_MinimumFlickVelocity)
2384 velocity = 0;
2385 const qreal maxVel = maximumFlickVelocity();
2386 if (maxVel > 0 && velocity > maxVel)
2387 velocity = maxVel;
2388 xVelocity = deltaX > 0 ? -velocity : velocity;
2389 }
2390
2391 if (yflick() && qAbs(deltaY) > 0.5) {
2392 const qreal decel = flickDeceleration();
2393 qreal velocity = qSqrt(2.0 * decel * qAbs(deltaY));
2394 if (qAbs(velocity) < _q_MinimumFlickVelocity)
2395 velocity = 0;
2396 const qreal maxVel = maximumFlickVelocity();
2397 if (maxVel > 0 && velocity > maxVel)
2398 velocity = maxVel;
2399 yVelocity = deltaY > 0 ? -velocity : velocity;
2400 }
2401
2402 if (qAbs(xVelocity) > 0.0 || qAbs(yVelocity) > 0.0) {
2403 flick(xVelocity, yVelocity);
2404 } else {
2405 if (newPosition.x() != currentPosition.x()) {
2406 setContentX(newPosition.x());
2407 d->fixupX();
2408 }
2409 if (newPosition.y() != currentPosition.y()) {
2410 setContentY(newPosition.y());
2411 d->fixupY();
2412 }
2413 }
2414}
2415
2416void QQuickFlickablePrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
2417{
2418 if (!prop || !prop->data)
2419 return;
2420
2421 if (QQuickItem *i = qmlobject_cast<QQuickItem *>(o)) {
2422 i->setParentItem(static_cast<QQuickFlickablePrivate*>(prop->data)->contentItem);
2423 } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) {
2424 static_cast<QQuickFlickablePrivate*>(prop->data)->addPointerHandler(pointerHandler);
2425 } else {
2426 o->setParent(prop->object); // XXX todo - do we want this?
2427 }
2428}
2429
2430qsizetype QQuickFlickablePrivate::data_count(QQmlListProperty<QObject> *)
2431{
2432 // XXX todo
2433 return 0;
2434}
2435
2436QObject *QQuickFlickablePrivate::data_at(QQmlListProperty<QObject> *, qsizetype)
2437{
2438 // XXX todo
2439 return nullptr;
2440}
2441
2442void QQuickFlickablePrivate::data_clear(QQmlListProperty<QObject> *)
2443{
2444 // XXX todo
2445}
2446
2447QQmlListProperty<QObject> QQuickFlickable::flickableData()
2448{
2449 Q_D(QQuickFlickable);
2450 return QQmlListProperty<QObject>(this, (void *)d, QQuickFlickablePrivate::data_append,
2451 QQuickFlickablePrivate::data_count,
2452 QQuickFlickablePrivate::data_at,
2453 QQuickFlickablePrivate::data_clear);
2454}
2455
2456QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren()
2457{
2458 Q_D(QQuickFlickable);
2459 return QQuickItemPrivate::get(d->contentItem)->children();
2460}
2461
2462/*!
2463 \qmlproperty enumeration QtQuick::Flickable::boundsBehavior
2464 This property holds whether the surface may be dragged
2465 beyond the Flickable's boundaries, or overshoot the
2466 Flickable's boundaries when flicked.
2467
2468 When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value
2469 other than \c Flickable.StopAtBounds will give a feeling that the edges of
2470 the view are soft, rather than a hard physical boundary.
2471
2472 The \c boundsBehavior can be one of:
2473
2474 \list
2475 \li Flickable.StopAtBounds - the contents can not be dragged beyond the boundary
2476 of the flickable, and flicks will not overshoot.
2477 \li Flickable.DragOverBounds - the contents can be dragged beyond the boundary
2478 of the Flickable, but flicks will not overshoot.
2479 \li Flickable.OvershootBounds - the contents can overshoot the boundary when flicked,
2480 but the content cannot be dragged beyond the boundary of the flickable. (since \c{QtQuick 2.5})
2481 \li Flickable.DragAndOvershootBounds (default) - the contents can be dragged
2482 beyond the boundary of the Flickable, and can overshoot the
2483 boundary when flicked.
2484 \endlist
2485
2486 \sa horizontalOvershoot, verticalOvershoot, boundsMovement
2487*/
2488QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const
2489{
2490 Q_D(const QQuickFlickable);
2491 return d->boundsBehavior;
2492}
2493
2494void QQuickFlickable::setBoundsBehavior(BoundsBehavior b)
2495{
2496 Q_D(QQuickFlickable);
2497 if (b == d->boundsBehavior)
2498 return;
2499 d->boundsBehavior = b;
2500 emit boundsBehaviorChanged();
2501}
2502
2503/*!
2504 \qmlproperty Transition QtQuick::Flickable::rebound
2505
2506 This holds the transition to be applied to the content view when
2507 it snaps back to the bounds of the flickable. The transition is
2508 triggered when the view is flicked or dragged past the edge of the
2509 content area, or when returnToBounds() is called.
2510
2511 \qml
2512 import QtQuick 2.0
2513
2514 Flickable {
2515 width: 150; height: 150
2516 contentWidth: 300; contentHeight: 300
2517
2518 rebound: Transition {
2519 NumberAnimation {
2520 properties: "x,y"
2521 duration: 1000
2522 easing.type: Easing.OutBounce
2523 }
2524 }
2525
2526 Rectangle {
2527 width: 300; height: 300
2528 gradient: Gradient {
2529 GradientStop { position: 0.0; color: "lightsteelblue" }
2530 GradientStop { position: 1.0; color: "blue" }
2531 }
2532 }
2533 }
2534 \endqml
2535
2536 When the above view is flicked beyond its bounds, it will return to its
2537 bounds using the transition specified:
2538
2539 \image flickable-rebound.gif
2540
2541 If this property is not set, a default animation is applied.
2542 */
2543QQuickTransition *QQuickFlickable::rebound() const
2544{
2545 Q_D(const QQuickFlickable);
2546 return d->rebound;
2547}
2548
2549void QQuickFlickable::setRebound(QQuickTransition *transition)
2550{
2551 Q_D(QQuickFlickable);
2552 if (transition) {
2553 if (!d->hData.transitionToBounds)
2554 d->hData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("x"));
2555 if (!d->vData.transitionToBounds)
2556 d->vData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("y"));
2557 }
2558 if (d->rebound != transition) {
2559 d->rebound = transition;
2560 emit reboundChanged();
2561 }
2562}
2563
2564/*!
2565 \qmlproperty real QtQuick::Flickable::contentWidth
2566 \qmlproperty real QtQuick::Flickable::contentHeight
2567
2568 The dimensions of the content (the surface controlled by Flickable).
2569 This should typically be set to the combined size of the items placed in the
2570 Flickable.
2571
2572 The following snippet shows how these properties are used to display
2573 an image that is larger than the Flickable item itself:
2574
2575 \snippet qml/flickable.qml document
2576
2577 In some cases, the content dimensions can be automatically set
2578 based on the \l {Item::childrenRect.width}{childrenRect.width}
2579 and \l {Item::childrenRect.height}{childrenRect.height} properties
2580 of the \l contentItem. For example, the previous snippet could be rewritten with:
2581
2582 \code
2583 contentWidth: contentItem.childrenRect.width; contentHeight: contentItem.childrenRect.height
2584 \endcode
2585
2586 Though this assumes that the origin of the childrenRect is 0,0.
2587*/
2588qreal QQuickFlickable::contentWidth() const
2589{
2590 Q_D(const QQuickFlickable);
2591 return d->hData.viewSize;
2592}
2593
2594void QQuickFlickable::setContentWidth(qreal w)
2595{
2596 Q_D(QQuickFlickable);
2597 if (d->hData.viewSize == w)
2598 return;
2599 d->hData.viewSize = w;
2600 if (w < 0)
2601 d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin);
2602 else
2603 d->contentItem->setWidth(w);
2604 d->hData.markExtentsDirty();
2605 // Make sure that we're entirely in view.
2606 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2607 d->fixupMode = QQuickFlickablePrivate::Immediate;
2608 d->fixupX();
2609 } else if (!d->pressed && d->hData.fixingUp) {
2610 d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
2611 d->fixupX();
2612 }
2613 emit contentWidthChanged();
2614 d->updateBeginningEnd();
2615}
2616
2617qreal QQuickFlickable::contentHeight() const
2618{
2619 Q_D(const QQuickFlickable);
2620 return d->vData.viewSize;
2621}
2622
2623void QQuickFlickable::setContentHeight(qreal h)
2624{
2625 Q_D(QQuickFlickable);
2626 if (d->vData.viewSize == h)
2627 return;
2628 d->vData.viewSize = h;
2629 if (h < 0)
2630 d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin);
2631 else
2632 d->contentItem->setHeight(h);
2633 d->vData.markExtentsDirty();
2634 // Make sure that we're entirely in view.
2635 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2636 d->fixupMode = QQuickFlickablePrivate::Immediate;
2637 d->fixupY();
2638 } else if (!d->pressed && d->vData.fixingUp) {
2639 d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
2640 d->fixupY();
2641 }
2642 emit contentHeightChanged();
2643 d->updateBeginningEnd();
2644}
2645
2646/*!
2647 \qmlproperty real QtQuick::Flickable::topMargin
2648 \qmlproperty real QtQuick::Flickable::leftMargin
2649 \qmlproperty real QtQuick::Flickable::bottomMargin
2650 \qmlproperty real QtQuick::Flickable::rightMargin
2651
2652 These properties hold the margins around the content. This space is reserved
2653 in addition to the contentWidth and contentHeight.
2654*/
2655
2656
2657qreal QQuickFlickable::topMargin() const
2658{
2659 Q_D(const QQuickFlickable);
2660 return d->vData.startMargin;
2661}
2662
2663void QQuickFlickable::setTopMargin(qreal m)
2664{
2665 Q_D(QQuickFlickable);
2666 if (d->vData.startMargin == m)
2667 return;
2668 d->vData.startMargin = m;
2669 d->vData.markExtentsDirty();
2670 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2671 // FIXME: We're not consistently updating the contentY, see QTBUG-131478
2672 d->fixupMode = QQuickFlickablePrivate::Immediate;
2673 d->fixupY();
2674 }
2675 emit topMarginChanged();
2676 d->updateBeginningEnd();
2677}
2678
2679qreal QQuickFlickable::bottomMargin() const
2680{
2681 Q_D(const QQuickFlickable);
2682 return d->vData.endMargin;
2683}
2684
2685void QQuickFlickable::setBottomMargin(qreal m)
2686{
2687 Q_D(QQuickFlickable);
2688 if (d->vData.endMargin == m)
2689 return;
2690 d->vData.endMargin = m;
2691 d->vData.markExtentsDirty();
2692 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2693 // FIXME: We're not consistently updating the contentY, see QTBUG-131478
2694 d->fixupMode = QQuickFlickablePrivate::Immediate;
2695 d->fixupY();
2696 }
2697 emit bottomMarginChanged();
2698 d->updateBeginningEnd();
2699}
2700
2701qreal QQuickFlickable::leftMargin() const
2702{
2703 Q_D(const QQuickFlickable);
2704 return d->hData.startMargin;
2705}
2706
2707void QQuickFlickable::setLeftMargin(qreal m)
2708{
2709 Q_D(QQuickFlickable);
2710 if (d->hData.startMargin == m)
2711 return;
2712 d->hData.startMargin = m;
2713 d->hData.markExtentsDirty();
2714 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2715 // FIXME: We're not consistently updating the contentX, see QTBUG-131478
2716 d->fixupMode = QQuickFlickablePrivate::Immediate;
2717 d->fixupX();
2718 }
2719 emit leftMarginChanged();
2720 d->updateBeginningEnd();
2721}
2722
2723qreal QQuickFlickable::rightMargin() const
2724{
2725 Q_D(const QQuickFlickable);
2726 return d->hData.endMargin;
2727}
2728
2729void QQuickFlickable::setRightMargin(qreal m)
2730{
2731 Q_D(QQuickFlickable);
2732 if (d->hData.endMargin == m)
2733 return;
2734 d->hData.endMargin = m;
2735 d->hData.markExtentsDirty();
2736 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2737 // FIXME: We're not consistently updating the contentX, see QTBUG-131478
2738 d->fixupMode = QQuickFlickablePrivate::Immediate;
2739 d->fixupX();
2740 }
2741 emit rightMarginChanged();
2742 d->updateBeginningEnd();
2743}
2744
2745/*!
2746 \qmlproperty real QtQuick::Flickable::originX
2747 \qmlproperty real QtQuick::Flickable::originY
2748
2749 These properties hold the origin of the content. This value always refers
2750 to the top-left position of the content regardless of layout direction.
2751
2752 This is usually (0,0), however ListView and GridView may have an arbitrary
2753 origin due to delegate size variation, or item insertion/removal outside
2754 the visible region.
2755
2756 \sa contentX, contentY
2757*/
2758
2759qreal QQuickFlickable::originY() const
2760{
2761 Q_D(const QQuickFlickable);
2762 return -minYExtent() + d->vData.startMargin;
2763}
2764
2765qreal QQuickFlickable::originX() const
2766{
2767 Q_D(const QQuickFlickable);
2768 return -minXExtent() + d->hData.startMargin;
2769}
2770
2771
2772/*!
2773 \qmlmethod QtQuick::Flickable::resizeContent(real width, real height, QPointF center)
2774
2775 Resizes the content to \a width x \a height about \a center.
2776
2777 This does not scale the contents of the Flickable - it only resizes the \l contentWidth
2778 and \l contentHeight.
2779
2780 Resizing the content may result in the content being positioned outside
2781 the bounds of the Flickable. Calling \l returnToBounds() will
2782 move the content back within legal bounds.
2783*/
2784void QQuickFlickable::resizeContent(qreal w, qreal h, QPointF center)
2785{
2786 Q_D(QQuickFlickable);
2787 const qreal oldHSize = d->hData.viewSize;
2788 const qreal oldVSize = d->vData.viewSize;
2789 const bool needToUpdateWidth = w != oldHSize;
2790 const bool needToUpdateHeight = h != oldVSize;
2791 d->hData.viewSize = w;
2792 d->vData.viewSize = h;
2793 d->contentItem->setSize(QSizeF(w, h));
2794 if (needToUpdateWidth)
2795 emit contentWidthChanged();
2796 if (needToUpdateHeight)
2797 emit contentHeightChanged();
2798
2799 if (center.x() != 0) {
2800 qreal pos = center.x() * w / oldHSize;
2801 setContentX(contentX() + pos - center.x());
2802 }
2803 if (center.y() != 0) {
2804 qreal pos = center.y() * h / oldVSize;
2805 setContentY(contentY() + pos - center.y());
2806 }
2807 d->updateBeginningEnd();
2808}
2809
2810/*!
2811 \qmlmethod QtQuick::Flickable::returnToBounds()
2812
2813 Ensures the content is within legal bounds.
2814
2815 This may be called to ensure that the content is within legal bounds
2816 after manually positioning the content.
2817*/
2818void QQuickFlickable::returnToBounds()
2819{
2820 Q_D(QQuickFlickable);
2821 d->fixupX();
2822 d->fixupY();
2823}
2824
2825qreal QQuickFlickable::vWidth() const
2826{
2827 Q_D(const QQuickFlickable);
2828 if (d->hData.viewSize < 0)
2829 return width();
2830 else
2831 return d->hData.viewSize;
2832}
2833
2834qreal QQuickFlickable::vHeight() const
2835{
2836 Q_D(const QQuickFlickable);
2837 if (d->vData.viewSize < 0)
2838 return height();
2839 else
2840 return d->vData.viewSize;
2841}
2842
2843/*!
2844 \internal
2845
2846 The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along.
2847
2848 \return true if the flickable is allowed to flick in the horizontal direction, otherwise returns false
2849*/
2850bool QQuickFlickable::xflick() const
2851{
2852 Q_D(const QQuickFlickable);
2853 const int contentWidthWithMargins = d->contentItem->width() + d->hData.startMargin + d->hData.endMargin;
2854 if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentWidthWithMargins > width()))
2855 return true;
2856 if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
2857 return std::floor(qAbs(contentWidthWithMargins - width()));
2858 return d->flickableDirection & QQuickFlickable::HorizontalFlick;
2859}
2860
2861/*!
2862 \internal
2863
2864 The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along.
2865
2866 \return true if the flickable is allowed to flick in the vertical direction, otherwise returns false.
2867*/
2868bool QQuickFlickable::yflick() const
2869{
2870 Q_D(const QQuickFlickable);
2871 const int contentHeightWithMargins = d->contentItem->height() + d->vData.startMargin + d->vData.endMargin;
2872 if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentHeightWithMargins > height()))
2873 return true;
2874 if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
2875 return std::floor(qAbs(contentHeightWithMargins - height()));
2876 return d->flickableDirection & QQuickFlickable::VerticalFlick;
2877}
2878
2879QPointF QQuickFlickable::computePosition(QPointF currentPosition, QRectF itemRect, PositionMode mode, const QPointF &offset) const
2880{
2881 QPointF newPosition = currentPosition;
2882
2883 if (xflick()) {
2884 const qreal viewWidth = width();
2885
2886 if (mode & QQuickFlickable::AlignLeft)
2887 newPosition.setX(itemRect.left());
2888 if (mode & QQuickFlickable::AlignHCenter)
2889 newPosition.setX(itemRect.left() - (viewWidth - itemRect.width()) / 2);
2890 if (mode & QQuickFlickable::AlignRight)
2891 newPosition.setX(itemRect.right() - viewWidth);
2892 if (mode & QQuickFlickable::Visible) {
2893 if (itemRect.right() < currentPosition.x())
2894 newPosition.setX(itemRect.left());
2895 else if (itemRect.left() > currentPosition.x() + viewWidth)
2896 newPosition.setX(itemRect.right() - viewWidth);
2897 }
2898 if (mode & QQuickFlickable::Contain) {
2899 if (itemRect.right() > currentPosition.x() + viewWidth)
2900 newPosition.setX(itemRect.right() - viewWidth);
2901 if (itemRect.left() < newPosition.x())
2902 newPosition.setX(itemRect.left());
2903 }
2904
2905 const qreal minX = -minXExtent();
2906 const qreal maxX = -maxXExtent();
2907 newPosition.setX(qMin(newPosition.x(), maxX));
2908 newPosition.setX(qMax(newPosition.x(), minX));
2909 }
2910
2911 if (yflick()) {
2912 const qreal viewHeight = height();
2913
2914 if (mode & QQuickFlickable::AlignTop)
2915 newPosition.setY(itemRect.top());
2916 if (mode & QQuickFlickable::AlignVCenter)
2917 newPosition.setY(itemRect.top() - (viewHeight - itemRect.height()) / 2);
2918 if (mode & QQuickFlickable::AlignBottom)
2919 newPosition.setY(itemRect.bottom() - viewHeight);
2920 if (mode & QQuickFlickable::Visible) {
2921 if (itemRect.bottom() < currentPosition.y())
2922 newPosition.setY(itemRect.top());
2923 else if (itemRect.top() > currentPosition.y() + viewHeight)
2924 newPosition.setY(itemRect.bottom() - viewHeight);
2925 }
2926 if (mode & QQuickFlickable::Contain) {
2927 if (itemRect.bottom() > currentPosition.y() + viewHeight)
2928 newPosition.setY(itemRect.bottom() - viewHeight);
2929 if (itemRect.top() < currentPosition.y())
2930 newPosition.setY(itemRect.top());
2931 }
2932
2933 const qreal minY = -minYExtent();
2934 const qreal maxY = -maxYExtent();
2935 newPosition.setY(qMin(newPosition.y(), maxY));
2936 newPosition.setY(qMax(newPosition.y(), minY));
2937 }
2938
2939 return newPosition + offset;
2940}
2941
2942void QQuickFlickable::mouseUngrabEvent()
2943{
2944 Q_D(QQuickFlickable);
2945 // if our mouse grab has been removed (probably by another Flickable),
2946 // fix our state
2947 if (!d->replayingPressEvent)
2948 d->cancelInteraction();
2949}
2950
2951void QQuickFlickablePrivate::cancelInteraction()
2952{
2953 Q_Q(QQuickFlickable);
2954 if (pressed) {
2955 clearDelayedPress();
2956 pressed = false;
2957 draggingEnding();
2958 stealMouse = false;
2959 q->setKeepMouseGrab(false);
2960 fixupX();
2961 fixupY();
2962 if (!isViewMoving())
2963 q->movementEnding();
2964 }
2965}
2966
2967void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h)
2968{
2969 Q_Q(const QQuickFlickable);
2970 qCDebug(lcHandlerParent) << "reparenting handler" << h << "to contentItem of" << q;
2971 h->setParent(contentItem);
2972 QQuickItemPrivate::get(contentItem)->addPointerHandler(h);
2973}
2974
2975/*! \internal
2976 QQuickFlickable::filterPointerEvent filters pointer events intercepted on the way
2977 to the child \a receiver, and potentially steals the exclusive grab.
2978
2979 This is how flickable takes over the handling of events from child items.
2980
2981 Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver.
2982*/
2983bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
2984{
2985 Q_D(QQuickFlickable);
2986 const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(event);
2987 const bool isMouse = QQuickDeliveryAgentPrivate::isMouseEvent(event);
2988 if (isMouse || QQuickDeliveryAgentPrivate::isTabletEvent(event)) {
2989 if (!d->buttonsAccepted(static_cast<QSinglePointEvent *>(event)))
2990 return QQuickItem::childMouseEventFilter(receiver, event);
2991 } else if (!isTouch) {
2992 return false; // don't filter hover events or wheel events, for example
2993 }
2994 Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself");
2995 // If a touch event contains a new press point, don't steal right away: watch the movements for a while
2996 if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed))
2997 d->stealMouse = false;
2998 // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child
2999 if (event->pointCount() > 1) {
3000 qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver;
3001 d->stealMouse = false;
3002 return false;
3003 } else {
3004 qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver;
3005 }
3006
3007 const auto &firstPoint = event->points().first();
3008
3009 if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) {
3010 // We have an exclusive grab (since we're e.g dragging), but at the same time, we have
3011 // a child with a passive grab (which is why this filter is being called). And because
3012 // of that, we end up getting the same pointer events twice; First in our own event
3013 // handlers (because of the grab), then once more in here, since we filter the child.
3014 // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more
3015 // from below), we mark the event as filtered, and simply return.
3016 event->setAccepted(true);
3017 return true;
3018 }
3019
3020 QPointF localPos = mapFromScene(firstPoint.scenePosition());
3021 bool receiverDisabled = receiver && !receiver->isEnabled();
3022 bool stealThisEvent = d->stealMouse;
3023 bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab());
3024 bool receiverRelinquishGrab = false;
3025
3026 // Special case for MouseArea, try to guess what it does with the event
3027 if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(receiver)) {
3028 bool preventStealing = mouseArea->preventStealing();
3029#if QT_CONFIG(quick_draganddrop)
3030 if (mouseArea->drag() && mouseArea->drag()->target())
3031 preventStealing = true;
3032#endif
3033 if (!preventStealing && receiverKeepsGrab) {
3034 receiverRelinquishGrab = !receiverDisabled || (isMouse
3035 && firstPoint.state() == QEventPoint::State::Pressed
3036 && (receiver->acceptedMouseButtons() & static_cast<QMouseEvent *>(event)->button()));
3037 if (receiverRelinquishGrab)
3038 receiverKeepsGrab = false;
3039 }
3040 }
3041
3042 if ((stealThisEvent || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) {
3043 QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos));
3044 localizedEvent->setAccepted(false);
3045 switch (firstPoint.state()) {
3046 case QEventPoint::State::Updated:
3047 d->handleMoveEvent(localizedEvent.data());
3048 break;
3049 case QEventPoint::State::Pressed:
3050 d->handlePressEvent(localizedEvent.data());
3051 d->captureDelayedPress(receiver, event);
3052 // never grab the pointing device on press during filtering: do it later, during a move
3053 d->stealMouse = false;
3054 stealThisEvent = false;
3055 break;
3056 case QEventPoint::State::Released:
3057 d->handleReleaseEvent(localizedEvent.data());
3058 stealThisEvent = d->stealMouse;
3059 break;
3060 case QEventPoint::State::Stationary:
3061 case QEventPoint::State::Unknown:
3062 break;
3063 }
3064 if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) {
3065 d->clearDelayedPress();
3066 event->setExclusiveGrabber(firstPoint, this);
3067 } else if (d->delayedPressEvent) {
3068 event->setExclusiveGrabber(firstPoint, this);
3069 }
3070 /*
3071 Note that d->stealMouse can be false before the call to d->handleMoveEvent(), but true
3072 afterwards. That means we detected a drag. But even so, we deliberately don't filter
3073 the move event that cause this to happen, since the user might actually be dragging on
3074 a child item, such as a Slider, in which case the child should get a chance to detect
3075 the drag instead, and take the grab. Only if we receive another move event after this,
3076 without the child grabbing the mouse in-between, do we initiate a flick.
3077 An exception is if the user is already in a flicking session started from an earlier
3078 drag, meaning that the content item is still moving (isMoving() == true). In that
3079 case, we see any subsequent drags as being a continuation of the flicking.
3080 Then we filter all events straight away, to avoid triggering any taps or drags in a child.
3081 */
3082 if (isMoving() || (!receiverRelinquishGrab && (stealThisEvent || d->delayedPressEvent || receiverDisabled))) {
3083 // Filter the event
3084 event->setAccepted(true);
3085 return true;
3086 }
3087
3088 // Don't filter the event
3089 return false;
3090 } else if (d->lastPosTime != -1) {
3091 d->lastPosTime = -1;
3092 returnToBounds();
3093 }
3094 if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) {
3095 // mouse released, or another item has claimed the grab
3096 d->lastPosTime = -1;
3097 d->clearDelayedPress();
3098 d->stealMouse = false;
3099 d->pressed = false;
3100 }
3101 return false;
3102}
3103
3104/*! \internal
3105 Despite the name, this function filters all pointer events on their way to any child within.
3106 Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver.
3107*/
3108bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e)
3109{
3110 Q_D(QQuickFlickable);
3111 QPointerEvent *pointerEvent = e->isPointerEvent() ? static_cast<QPointerEvent *>(e) : nullptr;
3112
3113 auto wantsPointerEvent_helper = [this, d, i, pointerEvent]() {
3114 Q_ASSERT(pointerEvent);
3115 QQuickDeliveryAgentPrivate::localizePointerEvent(pointerEvent, this);
3116 const bool wants = d->wantsPointerEvent(pointerEvent);
3117 // re-localize event back to \a i before returning
3118 QQuickDeliveryAgentPrivate::localizePointerEvent(pointerEvent, i);
3119 return wants;
3120 };
3121
3122 if (!isVisible() || !isEnabled() || !isInteractive() ||
3123 (pointerEvent && !wantsPointerEvent_helper())) {
3124 d->cancelInteraction();
3125 return QQuickItem::childMouseEventFilter(i, e);
3126 }
3127
3128 if (e->type() == QEvent::UngrabMouse) {
3129 Q_ASSERT(e->isSinglePointEvent());
3130 auto spe = static_cast<QSinglePointEvent *>(e);
3131 const QObject *grabber = spe->exclusiveGrabber(spe->points().first());
3132 qCDebug(lcFilter) << "filtering UngrabMouse" << spe->points().first() << "for" << i << "grabber is" << grabber;
3133 if (grabber != this)
3134 mouseUngrabEvent(); // A child has been ungrabbed
3135 } else if (pointerEvent) {
3136 return filterPointerEvent(i, pointerEvent);
3137 }
3138
3139 return QQuickItem::childMouseEventFilter(i, e);
3140}
3141
3142/*!
3143 \qmlproperty real QtQuick::Flickable::maximumFlickVelocity
3144 This property holds the maximum velocity that the user can flick the view in pixels/second.
3145
3146 The default value is platform dependent.
3147*/
3148qreal QQuickFlickable::maximumFlickVelocity() const
3149{
3150 Q_D(const QQuickFlickable);
3151 return d->maxVelocity;
3152}
3153
3154void QQuickFlickable::setMaximumFlickVelocity(qreal v)
3155{
3156 Q_D(QQuickFlickable);
3157 if (v == d->maxVelocity)
3158 return;
3159 d->maxVelocity = v;
3160 emit maximumFlickVelocityChanged();
3161}
3162
3163/*!
3164 \qmlproperty real QtQuick::Flickable::flickDeceleration
3165 This property holds the rate at which a flick will decelerate:
3166 the higher the number, the faster it slows down when the user stops
3167 flicking via touch. For example 0.0001 is nearly
3168 "frictionless", and 10000 feels quite "sticky".
3169
3170 The default value is platform dependent. Values of zero or less are not allowed.
3171*/
3172qreal QQuickFlickable::flickDeceleration() const
3173{
3174 Q_D(const QQuickFlickable);
3175 return d->deceleration;
3176}
3177
3178void QQuickFlickable::setFlickDeceleration(qreal deceleration)
3179{
3180 Q_D(QQuickFlickable);
3181 if (deceleration == d->deceleration)
3182 return;
3183 d->deceleration = qMax(0.001, deceleration);
3184 emit flickDecelerationChanged();
3185}
3186
3187bool QQuickFlickable::isFlicking() const
3188{
3189 Q_D(const QQuickFlickable);
3190 return d->hData.flicking || d->vData.flicking;
3191}
3192
3193/*!
3194 \qmlproperty bool QtQuick::Flickable::flicking
3195 \qmlproperty bool QtQuick::Flickable::flickingHorizontally
3196 \qmlproperty bool QtQuick::Flickable::flickingVertically
3197
3198 These properties describe whether the view is currently moving horizontally,
3199 vertically or in either direction, due to the user flicking the view.
3200*/
3201bool QQuickFlickable::isFlickingHorizontally() const
3202{
3203 Q_D(const QQuickFlickable);
3204 return d->hData.flicking;
3205}
3206
3207bool QQuickFlickable::isFlickingVertically() const
3208{
3209 Q_D(const QQuickFlickable);
3210 return d->vData.flicking;
3211}
3212
3213/*!
3214 \qmlproperty bool QtQuick::Flickable::dragging
3215 \qmlproperty bool QtQuick::Flickable::draggingHorizontally
3216 \qmlproperty bool QtQuick::Flickable::draggingVertically
3217
3218 These properties describe whether the view is currently moving horizontally,
3219 vertically or in either direction, due to the user dragging the view.
3220*/
3221bool QQuickFlickable::isDragging() const
3222{
3223 Q_D(const QQuickFlickable);
3224 return d->hData.dragging || d->vData.dragging;
3225}
3226
3227bool QQuickFlickable::isDraggingHorizontally() const
3228{
3229 Q_D(const QQuickFlickable);
3230 return d->hData.dragging;
3231}
3232
3233bool QQuickFlickable::isDraggingVertically() const
3234{
3235 Q_D(const QQuickFlickable);
3236 return d->vData.dragging;
3237}
3238
3239void QQuickFlickablePrivate::draggingStarting()
3240{
3241 Q_Q(QQuickFlickable);
3242 bool wasDragging = hData.dragging || vData.dragging;
3243 if (hMoved && !hData.dragging) {
3244 hData.dragging = true;
3245 emit q->draggingHorizontallyChanged();
3246 }
3247 if (vMoved && !vData.dragging) {
3248 vData.dragging = true;
3249 emit q->draggingVerticallyChanged();
3250 }
3251 if (!wasDragging && (hData.dragging || vData.dragging)) {
3252 emit q->draggingChanged();
3253 emit q->dragStarted();
3254 }
3255}
3256
3257void QQuickFlickablePrivate::draggingEnding()
3258{
3259 Q_Q(QQuickFlickable);
3260 const bool wasDragging = hData.dragging || vData.dragging;
3261 if (hData.dragging) {
3262 hData.dragging = false;
3263 emit q->draggingHorizontallyChanged();
3264 }
3265 if (vData.dragging) {
3266 vData.dragging = false;
3267 emit q->draggingVerticallyChanged();
3268 }
3269 if (wasDragging) {
3270 if (!hData.dragging && !vData.dragging) {
3271 emit q->draggingChanged();
3272 emit q->dragEnded();
3273 }
3274 hData.inRebound = false;
3275 vData.inRebound = false;
3276 }
3277}
3278
3279bool QQuickFlickablePrivate::isViewMoving() const
3280{
3281 if (timeline.isActive()
3282 || (hData.transitionToBounds && hData.transitionToBounds->isActive())
3283 || (vData.transitionToBounds && vData.transitionToBounds->isActive()) ) {
3284 return true;
3285 }
3286 return false;
3287}
3288
3289/*!
3290 \qmlproperty int QtQuick::Flickable::pressDelay
3291
3292 This property holds the time to delay (ms) delivering a press to
3293 children of the Flickable. This can be useful where reacting
3294 to a press before a flicking action has undesirable effects.
3295
3296 If the flickable is dragged/flicked before the delay times out
3297 the press event will not be delivered. If the button is released
3298 within the timeout, both the press and release will be delivered.
3299
3300 Note that for nested Flickables with pressDelay set, the pressDelay of
3301 outer Flickables is overridden by the innermost Flickable. If the drag
3302 exceeds the platform drag threshold, the press event will be delivered
3303 regardless of this property.
3304
3305 \sa QStyleHints
3306*/
3307int QQuickFlickable::pressDelay() const
3308{
3309 Q_D(const QQuickFlickable);
3310 return d->pressDelay;
3311}
3312
3313void QQuickFlickable::setPressDelay(int delay)
3314{
3315 Q_D(QQuickFlickable);
3316 if (d->pressDelay == delay)
3317 return;
3318 d->pressDelay = delay;
3319 emit pressDelayChanged();
3320}
3321
3322/*!
3323 \qmlproperty bool QtQuick::Flickable::moving
3324 \qmlproperty bool QtQuick::Flickable::movingHorizontally
3325 \qmlproperty bool QtQuick::Flickable::movingVertically
3326
3327 These properties describe whether the view is currently moving horizontally,
3328 vertically or in either direction, due to the user either dragging or
3329 flicking the view.
3330*/
3331
3332bool QQuickFlickable::isMoving() const
3333{
3334 Q_D(const QQuickFlickable);
3335 return d->hData.moving || d->vData.moving;
3336}
3337
3338bool QQuickFlickable::isMovingHorizontally() const
3339{
3340 Q_D(const QQuickFlickable);
3341 return d->hData.moving;
3342}
3343
3344bool QQuickFlickable::isMovingVertically() const
3345{
3346 Q_D(const QQuickFlickable);
3347 return d->vData.moving;
3348}
3349
3350void QQuickFlickable::velocityTimelineCompleted()
3351{
3352 Q_D(QQuickFlickable);
3353 if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive())
3354 || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) {
3355 return;
3356 }
3357 // With subclasses such as GridView, velocityTimeline.completed is emitted repeatedly:
3358 // for example setting currentIndex results in a visual "flick" which the user
3359 // didn't initiate directly. We don't want to end movement repeatedly, and in
3360 // that case movementEnding will happen after the sequence of movements ends.
3361 if (d->vData.flicking)
3362 movementEnding();
3363 d->updateBeginningEnd();
3364}
3365
3366void QQuickFlickable::timelineCompleted()
3367{
3368 Q_D(QQuickFlickable);
3369 if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive())
3370 || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) {
3371 return;
3372 }
3373 movementEnding();
3374 d->updateBeginningEnd();
3375}
3376
3377void QQuickFlickable::movementStarting()
3378{
3379 Q_D(QQuickFlickable);
3380 bool wasMoving = d->hData.moving || d->vData.moving;
3381 if (d->hMoved && !d->hData.moving) {
3382 d->hData.moving = true;
3383 emit movingHorizontallyChanged();
3384 }
3385 if (d->vMoved && !d->vData.moving) {
3386 d->vData.moving = true;
3387 emit movingVerticallyChanged();
3388 }
3389
3390 if (!wasMoving && (d->hData.moving || d->vData.moving)) {
3391 emit movingChanged();
3392 emit movementStarted();
3393#if QT_CONFIG(accessibility)
3394 if (QAccessible::isActive()) {
3395 QAccessibleEvent ev(this, QAccessible::ScrollingStart);
3396 QAccessible::updateAccessibility(&ev);
3397 }
3398#endif
3399 }
3400}
3401
3402void QQuickFlickable::movementEnding()
3403{
3404 movementEnding(true, true);
3405}
3406
3407void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding)
3408{
3409 Q_D(QQuickFlickable);
3410
3411 // emit flicking signals
3412 const bool wasFlicking = d->hData.flicking || d->vData.flicking;
3413 if (hMovementEnding && d->hData.flicking) {
3414 d->hData.flicking = false;
3415 emit flickingHorizontallyChanged();
3416 }
3417 if (vMovementEnding && d->vData.flicking) {
3418 d->vData.flicking = false;
3419 emit flickingVerticallyChanged();
3420 }
3421 if (wasFlicking && (!d->hData.flicking || !d->vData.flicking)) {
3422 emit flickingChanged();
3423 emit flickEnded();
3424 } else if (d->hData.flickingWhenDragBegan || d->vData.flickingWhenDragBegan) {
3425 d->hData.flickingWhenDragBegan = !hMovementEnding;
3426 d->vData.flickingWhenDragBegan = !vMovementEnding;
3427 emit flickEnded();
3428 }
3429
3430 // emit moving signals
3431 bool wasMoving = isMoving();
3432 if (hMovementEnding && d->hData.moving
3433 && (!d->pressed && !d->stealMouse)) {
3434 d->hData.moving = false;
3435 d->hMoved = false;
3436 emit movingHorizontallyChanged();
3437 }
3438 if (vMovementEnding && d->vData.moving
3439 && (!d->pressed && !d->stealMouse)) {
3440 d->vData.moving = false;
3441 d->vMoved = false;
3442 emit movingVerticallyChanged();
3443 }
3444 if (wasMoving && !isMoving()) {
3445 emit movingChanged();
3446 emit movementEnded();
3447#if QT_CONFIG(accessibility)
3448 if (QAccessible::isActive()) {
3449 QAccessibleEvent ev(this, QAccessible::ScrollingEnd);
3450 QAccessible::updateAccessibility(&ev);
3451 }
3452#endif
3453 }
3454
3455 if (hMovementEnding) {
3456 d->hData.fixingUp = false;
3457 d->hData.smoothVelocity.setValue(0);
3458 d->hData.previousDragDelta = 0.0;
3459 }
3460 if (vMovementEnding) {
3461 d->vData.fixingUp = false;
3462 d->vData.smoothVelocity.setValue(0);
3463 d->vData.previousDragDelta = 0.0;
3464 }
3465}
3466
3467void QQuickFlickablePrivate::updateVelocity()
3468{
3469 Q_Q(QQuickFlickable);
3470 emit q->horizontalVelocityChanged();
3471 emit q->verticalVelocityChanged();
3472}
3473
3474/*!
3475 \qmlproperty real QtQuick::Flickable::horizontalOvershoot
3476 \since 5.9
3477
3478 This property holds the horizontal overshoot, that is, the horizontal distance by
3479 which the contents has been dragged or flicked past the bounds of the flickable.
3480 The value is negative when the content is dragged or flicked beyond the beginning,
3481 and positive when beyond the end; \c 0.0 otherwise.
3482
3483 Whether the values are reported for dragging and/or flicking is determined by
3484 \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement
3485 is \c Flickable.StopAtBounds.
3486
3487 \sa verticalOvershoot, boundsBehavior, boundsMovement
3488*/
3489qreal QQuickFlickable::horizontalOvershoot() const
3490{
3491 Q_D(const QQuickFlickable);
3492 return d->hData.overshoot;
3493}
3494
3495/*!
3496 \qmlproperty real QtQuick::Flickable::verticalOvershoot
3497 \since 5.9
3498
3499 This property holds the vertical overshoot, that is, the vertical distance by
3500 which the contents has been dragged or flicked past the bounds of the flickable.
3501 The value is negative when the content is dragged or flicked beyond the beginning,
3502 and positive when beyond the end; \c 0.0 otherwise.
3503
3504 Whether the values are reported for dragging and/or flicking is determined by
3505 \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement
3506 is \c Flickable.StopAtBounds.
3507
3508 \sa horizontalOvershoot, boundsBehavior, boundsMovement
3509*/
3510qreal QQuickFlickable::verticalOvershoot() const
3511{
3512 Q_D(const QQuickFlickable);
3513 return d->vData.overshoot;
3514}
3515
3516/*!
3517 \qmlproperty enumeration QtQuick::Flickable::boundsMovement
3518 \since 5.10
3519
3520 This property holds whether the flickable will give a feeling that the edges of the
3521 view are soft, rather than a hard physical boundary.
3522
3523 The \c boundsMovement can be one of:
3524
3525 \list
3526 \li Flickable.StopAtBounds - this allows implementing custom edge effects where the
3527 contents do not follow drags or flicks beyond the bounds of the flickable. The values
3528 of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom
3529 edge effects.
3530 \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or
3531 flicks beyond the bounds of the flickable is determined by \l boundsBehavior.
3532 \endlist
3533
3534 The following example keeps the contents within bounds and instead applies a flip
3535 effect when flicked over horizontal bounds:
3536 \code
3537 Flickable {
3538 id: flickable
3539 boundsMovement: Flickable.StopAtBounds
3540 boundsBehavior: Flickable.DragAndOvershootBounds
3541 transform: Rotation {
3542 axis { x: 0; y: 1; z: 0 }
3543 origin.x: flickable.width / 2
3544 origin.y: flickable.height / 2
3545 angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot))
3546 }
3547 }
3548 \endcode
3549
3550 The following example keeps the contents within bounds and instead applies an opacity
3551 effect when dragged over vertical bounds:
3552 \code
3553 Flickable {
3554 boundsMovement: Flickable.StopAtBounds
3555 boundsBehavior: Flickable.DragOverBounds
3556 opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height)
3557 }
3558 \endcode
3559
3560 \sa boundsBehavior, verticalOvershoot, horizontalOvershoot
3561*/
3562QQuickFlickable::BoundsMovement QQuickFlickable::boundsMovement() const
3563{
3564 Q_D(const QQuickFlickable);
3565 return d->boundsMovement;
3566}
3567
3568void QQuickFlickable::setBoundsMovement(BoundsMovement movement)
3569{
3570 Q_D(QQuickFlickable);
3571 if (d->boundsMovement == movement)
3572 return;
3573
3574 d->boundsMovement = movement;
3575 emit boundsMovementChanged();
3576}
3577
3578QT_END_NAMESPACE
3579
3580#include "moc_qquickflickable_p_p.cpp"
3581
3582#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