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