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 if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull() || event->phase() == Qt::NoScrollPhase) {
1779 // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta
1780 int xDelta = event->angleDelta().x();
1781 int yDelta = event->angleDelta().y();
1782
1783 if (d->wheelDeceleration > _q_MaximumWheelDeceleration) {
1784 const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24;
1785 // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel
1786 // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines()
1787 if (yflick() && yDelta != 0) {
1788 d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
1789 d->vMoved = true;
1790 qreal scrollPixel = (-yDelta / 120.0 * wheelScroll);
1791 bool acceptEvent = true; // Set to false if event should propagate to parent
1792 if (scrollPixel > 0) { // Forward direction (away from user)
1793 if (d->vData.move.value() >= minYExtent()) {
1794 d->vMoved = false;
1795 acceptEvent = false;
1796 }
1797 } else { // Backward direction (towards user)
1798 if (d->vData.move.value() <= maxYExtent()) {
1799 d->vMoved = false;
1800 acceptEvent = false;
1801 }
1802 }
1803 if (d->vMoved) {
1804 if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
1805 const qreal estContentPos = scrollPixel + d->vData.move.value();
1806 if (scrollPixel > 0) { // Forward direction (away from user)
1807 if (estContentPos > minYExtent()) {
1808 scrollPixel = minYExtent() - d->vData.move.value();
1809 acceptEvent = false;
1810 }
1811 } else { // Backward direction (towards user)
1812 if (estContentPos < maxYExtent()) {
1813 scrollPixel = maxYExtent() - d->vData.move.value();
1814 acceptEvent = false;
1815 }
1816 }
1817 }
1818 d->resetTimeline(d->vData);
1819 movementStarting();
1820 d->timeline.moveBy(d->vData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4);
1821 d->vData.fixingUp = true;
1822 d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d));
1823 }
1824 if (acceptEvent)
1825 event->accept();
1826 }
1827 if (xflick() && xDelta != 0) {
1828 d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this.
1829 d->hMoved = true;
1830 qreal scrollPixel = (-xDelta / 120.0 * wheelScroll);
1831 bool acceptEvent = true; // Set to false if event should propagate to parent
1832 if (scrollPixel > 0) { // Forward direction (away from user)
1833 if (d->hData.move.value() >= minXExtent()) {
1834 d->hMoved = false;
1835 acceptEvent = false;
1836 }
1837 } else { // Backward direction (towards user)
1838 if (d->hData.move.value() <= maxXExtent()) {
1839 d->hMoved = false;
1840 acceptEvent = false;
1841 }
1842 }
1843 if (d->hMoved) {
1844 if (d->boundsBehavior == QQuickFlickable::StopAtBounds) {
1845 const qreal estContentPos = scrollPixel + d->hData.move.value();
1846 if (scrollPixel > 0) { // Forward direction (away from user)
1847 if (estContentPos > minXExtent()) {
1848 scrollPixel = minXExtent() - d->hData.move.value();
1849 acceptEvent = false;
1850 }
1851 } else { // Backward direction (towards user)
1852 if (estContentPos < maxXExtent()) {
1853 scrollPixel = maxXExtent() - d->hData.move.value();
1854 acceptEvent = false;
1855 }
1856 }
1857 }
1858 d->resetTimeline(d->hData);
1859 movementStarting();
1860 d->timeline.moveBy(d->hData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4);
1861 d->hData.fixingUp = true;
1862 d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d));
1863 }
1864 if (acceptEvent)
1865 event->accept();
1866 }
1867 } else {
1868 // wheelDeceleration is set to some reasonable value: the user or the platform wants to have
1869 // the classic Qt Quick mouse wheel acceleration behavior.
1870 // For a single "clicky" wheel event (angleDelta +/- 120),
1871 // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines().
1872 // The decel algo from there is
1873 // qreal dist = v2 / (accel * 2.0);
1874 // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2)
1875 // now solve for dt:
1876 // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance)
1877 if (!isMoving())
1878 elapsed = 120 / qSqrt(d->wheelDeceleration * 2 * d->initialWheelFlickDistance);
1879 if (yflick() && yDelta != 0) {
1880 qreal instVelocity = yDelta / elapsed;
1881 // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
1882 if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0)) {
1883 d->vData.velocitySamples = 0;
1884 d->vData.velocityWritePos = 0;
1885 }
1886 d->vData.addVelocitySample(instVelocity, d->maxVelocity);
1887 d->vData.updateVelocity();
1888 if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) {
1889 const bool newFlick = d->flickY(event->type(), d->vData.velocity);
1890 if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) {
1891 d->flickingStarted(false, true);
1892 d->vMoved = true;
1893 movementStarting();
1894 }
1895 event->accept();
1896 }
1897 }
1898 if (xflick() && xDelta != 0) {
1899 qreal instVelocity = xDelta / elapsed;
1900 // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction
1901 if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0)) {
1902 d->hData.velocitySamples = 0;
1903 d->hData.velocityWritePos = 0;
1904 }
1905 d->hData.addVelocitySample(instVelocity, d->maxVelocity);
1906 d->hData.updateVelocity();
1907 if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) {
1908 const bool newFlick = d->flickX(event->type(), d->hData.velocity);
1909 if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) {
1910 d->flickingStarted(true, false);
1911 d->hMoved = true;
1912 movementStarting();
1913 }
1914 event->accept();
1915 }
1916 }
1917 }
1918 } else {
1919 // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually
1920 int xDelta = event->pixelDelta().x();
1921 int yDelta = event->pixelDelta().y();
1922
1923 QVector2D velocity(xDelta / elapsed, yDelta / elapsed);
1924 d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta());
1925 // Try to drag if 1) we already are dragging or flicking, or
1926 // 2) the flickable is free to flick both directions, or
1927 // 3) the movement so far has been mostly horizontal AND it's free to flick horizontally, or
1928 // 4) the movement so far has been mostly vertical AND it's free to flick vertically.
1929 // Otherwise, wait until the next event. Wheel events with pixel deltas tend to come frequently.
1930 if (isMoving() || isFlicking() || (yflick() && xflick())
1931 || (xflick() && qAbs(d->accumulatedWheelPixelDelta.x()) > qAbs(d->accumulatedWheelPixelDelta.y() * 2))
1932 || (yflick() && qAbs(d->accumulatedWheelPixelDelta.y()) > qAbs(d->accumulatedWheelPixelDelta.x() * 2))) {
1933 d->drag(currentTimestamp, event->type(), event->position(), d->accumulatedWheelPixelDelta,
1934 true, !d->scrollingPhase, true, velocity);
1935 d->updateBeginningEnd();
1936 if ((xflick() && !isAtXBeginning() && !isAtXEnd()) || (yflick() && !isAtYBeginning() && !isAtYEnd()))
1937 event->accept();
1938 } else {
1939 qCDebug(lcWheel) << "not dragging: accumulated deltas" << d->accumulatedWheelPixelDelta <<
1940 "moving?" << isMoving() << "can flick horizontally?" << xflick() << "vertically?" << yflick();
1941 }
1942 }
1943 d->lastPosTime = currentTimestamp;
1944
1945 if (!event->isAccepted())
1946 QQuickItem::wheelEvent(event);
1947}
1948#endif
1949
1950bool QQuickFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const
1951{
1952 Q_Q(const QQuickFlickable);
1953 QQuickItem *item = i;
1954 while (item) {
1955 QQuickFlickable *flick = qobject_cast<QQuickFlickable*>(item);
1956 if (flick && flick->pressDelay() > 0 && flick->isInteractive()) {
1957 // Found the innermost flickable with press delay - is it me?
1958 return (flick == q);
1959 }
1960 item = item->parentItem();
1961 }
1962 return false;
1963}
1964
1965void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent *event)
1966{
1967 Q_Q(QQuickFlickable);
1968 if (!q->window() || pressDelay <= 0)
1969 return;
1970
1971 // Only the innermost flickable should handle the delayed press; this allows
1972 // flickables up the parent chain to all see the events in their filter functions
1973 if (!isInnermostPressDelay(item))
1974 return;
1975
1976 delayedPressEvent = QQuickDeliveryAgentPrivate::clonePointerEvent(event);
1977 delayedPressEvent->setAccepted(false);
1978 delayedPressTimer.start(pressDelay, q);
1979 qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent;
1980}
1981
1982void QQuickFlickablePrivate::clearDelayedPress()
1983{
1984 if (delayedPressEvent) {
1985 delayedPressTimer.stop();
1986 qCDebug(lcReplay) << "clear delayed press" << delayedPressEvent;
1987 delete delayedPressEvent;
1988 delayedPressEvent = nullptr;
1989 }
1990}
1991
1992void QQuickFlickablePrivate::replayDelayedPress()
1993{
1994 Q_Q(QQuickFlickable);
1995 if (delayedPressEvent) {
1996 // Losing the grab will clear the delayed press event; take control of it here
1997 QScopedPointer<QPointerEvent> event(delayedPressEvent);
1998 delayedPressEvent = nullptr;
1999 delayedPressTimer.stop();
2000
2001 // If we have the grab, release before delivering the event
2002 if (QQuickWindow *window = q->window()) {
2003 auto da = deliveryAgentPrivate();
2004 da->allowChildEventFiltering = false; // don't allow re-filtering during replay
2005 replayingPressEvent = true;
2006 auto &firstPoint = event->point(0);
2007 // At first glance, it's weird for delayedPressEvent to already have a grabber;
2008 // but on press, filterMouseEvent() took the exclusive grab, and that's stored
2009 // in the device-specific EventPointData instance in QPointingDevicePrivate::activePoints,
2010 // not in the event itself. If this Flickable is still the grabber of that point on that device,
2011 // that's the reason; but now it doesn't need that grab anymore.
2012 if (event->exclusiveGrabber(firstPoint) == q)
2013 event->setExclusiveGrabber(firstPoint, nullptr);
2014
2015 qCDebug(lcReplay) << "replaying" << event.data();
2016 // Put scenePosition into position, for the sake of QQuickWindowPrivate::translateTouchEvent()
2017 // TODO remove this if we remove QQuickWindowPrivate::translateTouchEvent()
2018 QMutableEventPoint::setPosition(firstPoint, firstPoint.scenePosition());
2019 // Send it through like a fresh press event, and let QQuickWindow
2020 // (more specifically, QQuickWindowPrivate::deliverPressOrReleaseEvent)
2021 // find the item or handler that should receive it, as usual.
2022 QCoreApplication::sendEvent(window, event.data());
2023 qCDebug(lcReplay) << "replay done";
2024
2025 // We're done with replay, go back to normal delivery behavior
2026 replayingPressEvent = false;
2027 da->allowChildEventFiltering = true;
2028 }
2029 }
2030}
2031
2032//XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned.
2033
2034/*!
2035 \internal
2036
2037 This function is called from the timeline,
2038 when advancement in the timeline is modifying the hData.move value.
2039 The \a x argument is the newly updated value in hData.move.
2040 The purpose of the function is to update the x position of the contentItem.
2041*/
2042void QQuickFlickablePrivate::setViewportX(qreal x)
2043{
2044 Q_Q(QQuickFlickable);
2045 qreal effectiveX = pixelAligned ? -std::round(-x) : x;
2046
2047 const qreal maxX = q->maxXExtent();
2048 const qreal minX = q->minXExtent();
2049
2050 if (boundsMovement == int(QQuickFlickable::StopAtBounds))
2051 effectiveX = qBound(maxX, effectiveX, minX);
2052
2053 contentItem->setX(effectiveX);
2054 if (contentItem->x() != effectiveX)
2055 return; // reentered
2056
2057 qreal overshoot = 0.0;
2058 if (x <= maxX)
2059 overshoot = maxX - x;
2060 else if (x >= minX)
2061 overshoot = minX - x;
2062
2063 if (overshoot != hData.overshoot) {
2064 hData.overshoot = overshoot;
2065 emit q->horizontalOvershootChanged();
2066 }
2067}
2068
2069/*!
2070 \internal
2071
2072 This function is called from the timeline,
2073 when advancement in the timeline is modifying the vData.move value.
2074 The \a y argument is the newly updated value in vData.move.
2075 The purpose of the function is to update the y position of the contentItem.
2076*/
2077void QQuickFlickablePrivate::setViewportY(qreal y)
2078{
2079 Q_Q(QQuickFlickable);
2080 qreal effectiveY = pixelAligned ? -std::round(-y) : y;
2081
2082 const qreal maxY = q->maxYExtent();
2083 const qreal minY = q->minYExtent();
2084
2085 if (boundsMovement == int(QQuickFlickable::StopAtBounds))
2086 effectiveY = qBound(maxY, effectiveY, minY);
2087
2088 contentItem->setY(effectiveY);
2089 if (contentItem->y() != effectiveY)
2090 return; // reentered
2091
2092 qreal overshoot = 0.0;
2093 if (y <= maxY)
2094 overshoot = maxY - y;
2095 else if (y >= minY)
2096 overshoot = minY - y;
2097
2098 if (overshoot != vData.overshoot) {
2099 vData.overshoot = overshoot;
2100 emit q->verticalOvershootChanged();
2101 }
2102}
2103
2104void QQuickFlickable::timerEvent(QTimerEvent *event)
2105{
2106 Q_D(QQuickFlickable);
2107 if (event->timerId() == d->delayedPressTimer.timerId()) {
2108 d->delayedPressTimer.stop();
2109 if (d->delayedPressEvent) {
2110 d->replayDelayedPress();
2111 }
2112 }
2113}
2114
2115qreal QQuickFlickable::minYExtent() const
2116{
2117 Q_D(const QQuickFlickable);
2118 return d->vData.startMargin;
2119}
2120
2121qreal QQuickFlickable::minXExtent() const
2122{
2123 Q_D(const QQuickFlickable);
2124 return d->hData.startMargin;
2125}
2126
2127/* returns -ve */
2128qreal QQuickFlickable::maxXExtent() const
2129{
2130 Q_D(const QQuickFlickable);
2131 return qMin<qreal>(minXExtent(), width() - vWidth() - d->hData.endMargin);
2132}
2133/* returns -ve */
2134qreal QQuickFlickable::maxYExtent() const
2135{
2136 Q_D(const QQuickFlickable);
2137 return qMin<qreal>(minYExtent(), height() - vHeight() - d->vData.endMargin);
2138}
2139
2140void QQuickFlickable::componentComplete()
2141{
2142 Q_D(QQuickFlickable);
2143 QQuickItem::componentComplete();
2144 if (!d->hData.explicitValue && d->hData.startMargin != 0.)
2145 setContentX(-minXExtent());
2146 if (!d->vData.explicitValue && d->vData.startMargin != 0.)
2147 setContentY(-minYExtent());
2148 if (lcWheel().isDebugEnabled() || lcVel().isDebugEnabled()) {
2149 d->timeline.setObjectName(QLatin1String("timeline for Flickable ") + objectName());
2150 d->velocityTimeline.setObjectName(QLatin1String("velocity timeline for Flickable ") + objectName());
2151 }
2152}
2153
2154void QQuickFlickable::viewportMoved(Qt::Orientations orient)
2155{
2156 Q_D(QQuickFlickable);
2157 if (orient & Qt::Vertical)
2158 d->viewportAxisMoved(d->vData, minYExtent(), maxYExtent(), d->fixupY_callback);
2159 if (orient & Qt::Horizontal)
2160 d->viewportAxisMoved(d->hData, minXExtent(), maxXExtent(), d->fixupX_callback);
2161 d->updateBeginningEnd();
2162}
2163
2164void QQuickFlickablePrivate::viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent,
2165 QQuickTimeLineCallback::Callback fixupCallback)
2166{
2167 if (!scrollingPhase && (pressed || calcVelocity)) {
2168 int elapsed = data.velocityTime.restart();
2169 if (elapsed > 0) {
2170 qreal velocity = (data.lastPos - data.move.value()) * 1000 / elapsed;
2171 if (qAbs(velocity) > 0) {
2172 velocityTimeline.reset(data.smoothVelocity);
2173 velocityTimeline.set(data.smoothVelocity, velocity);
2174 qCDebug(lcVel) << "touchpad scroll phase: velocity" << velocity;
2175 }
2176 }
2177 } else {
2178 if (timeline.time() > data.vTime) {
2179 velocityTimeline.reset(data.smoothVelocity);
2180 int dt = timeline.time() - data.vTime;
2181 if (dt > 2) {
2182 qreal velocity = (data.lastPos - data.move.value()) * 1000 / dt;
2183 if (!qFuzzyCompare(data.smoothVelocity.value(), velocity))
2184 qCDebug(lcVel) << "velocity" << data.smoothVelocity.value() << "->" << velocity
2185 << "computed as (" << data.lastPos << "-" << data.move.value() << ") * 1000 / ("
2186 << timeline.time() << "-" << data.vTime << ")";
2187 data.smoothVelocity.setValue(velocity);
2188 }
2189 }
2190 }
2191
2192 if (!data.inOvershoot && !data.fixingUp && data.flicking
2193 && (data.move.value() > minExtent || data.move.value() < maxExtent)
2194 && qAbs(data.smoothVelocity.value()) > 10) {
2195 // Increase deceleration if we've passed a bound
2196 qreal overBound = data.move.value() > minExtent
2197 ? data.move.value() - minExtent
2198 : maxExtent - data.move.value();
2199 data.inOvershoot = true;
2200 qreal maxDistance = overShootDistance(qAbs(data.smoothVelocity.value())) - overBound;
2201 resetTimeline(data);
2202 if (maxDistance > 0)
2203 timeline.accel(data.move, -data.smoothVelocity.value(), deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
2204 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
2205 }
2206
2207 data.lastPos = data.move.value();
2208 data.vTime = timeline.time();
2209}
2210
2211void QQuickFlickable::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2212{
2213 Q_D(QQuickFlickable);
2214 QQuickItem::geometryChange(newGeometry, oldGeometry);
2215
2216 bool changed = false;
2217 if (newGeometry.width() != oldGeometry.width()) {
2218 changed = true; // we must update visualArea.widthRatio
2219 if (d->hData.viewSize < 0)
2220 d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin);
2221 // Make sure that we're entirely in view.
2222 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2223 d->fixupMode = QQuickFlickablePrivate::Immediate;
2224 d->fixupX();
2225 }
2226 }
2227 if (newGeometry.height() != oldGeometry.height()) {
2228 changed = true; // we must update visualArea.heightRatio
2229 if (d->vData.viewSize < 0)
2230 d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin);
2231 // Make sure that we're entirely in view.
2232 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2233 d->fixupMode = QQuickFlickablePrivate::Immediate;
2234 d->fixupY();
2235 }
2236 }
2237
2238 if (changed)
2239 d->updateBeginningEnd();
2240}
2241
2242/*!
2243 \qmlmethod void QtQuick::Flickable::flick(qreal xVelocity, qreal yVelocity)
2244
2245 Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec.
2246
2247 Calling this method will update the corresponding moving and flicking properties and signals,
2248 just like a real touchscreen flick.
2249*/
2250
2251void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity)
2252{
2253 Q_D(QQuickFlickable);
2254 d->hData.reset();
2255 d->vData.reset();
2256 d->hData.velocity = xVelocity;
2257 d->vData.velocity = yVelocity;
2258 d->hData.vTime = d->vData.vTime = d->timeline.time();
2259
2260 const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(QEvent::TouchUpdate, xVelocity);
2261 const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(QEvent::TouchUpdate, yVelocity);
2262
2263 if (flickedX)
2264 d->hMoved = true;
2265 if (flickedY)
2266 d->vMoved = true;
2267 movementStarting();
2268 d->flickingStarted(flickedX, flickedY);
2269}
2270
2271void QQuickFlickablePrivate::flickingStarted(bool flickingH, bool flickingV)
2272{
2273 Q_Q(QQuickFlickable);
2274 if (!flickingH && !flickingV)
2275 return;
2276
2277 bool wasFlicking = hData.flicking || vData.flicking;
2278 if (flickingH && !hData.flicking) {
2279 hData.flicking = true;
2280 emit q->flickingHorizontallyChanged();
2281 }
2282 if (flickingV && !vData.flicking) {
2283 vData.flicking = true;
2284 emit q->flickingVerticallyChanged();
2285 }
2286 if (!wasFlicking && (hData.flicking || vData.flicking)) {
2287 emit q->flickingChanged();
2288 emit q->flickStarted();
2289 }
2290}
2291
2292/*!
2293 \qmlmethod void QtQuick::Flickable::cancelFlick()
2294
2295 Cancels the current flick animation.
2296*/
2297
2298void QQuickFlickable::cancelFlick()
2299{
2300 Q_D(QQuickFlickable);
2301 d->resetTimeline(d->hData);
2302 d->resetTimeline(d->vData);
2303 movementEnding();
2304}
2305
2306/*!
2307 \qmlmethod void QtQuick::Flickable::positionViewAtChild(QQuickItem *child, PositionMode mode, point offset)
2308 \since 6.11
2309
2310 Positions \l {Flickable::}{contentX} and \l {Flickable::}{contentY} such
2311 that \a child item (if it is a child) is at the position specified by \a mode. \a mode
2312 can be an or-ed combination of the following:
2313
2314 \value Flickable.AlignLeft Position the child at the left of the view.
2315 \value Flickable.AlignHCenter Position the child at the horizontal center of the view.
2316 \value Flickable.AlignRight Position the child at the right of the view.
2317 \value Flickable.AlignTop Position the child at the top of the view.
2318 \value Flickable.AlignVCenter Position the child at the vertical center of the view.
2319 \value Flickable.AlignBottom Position the child at the bottom of the view.
2320 \value Flickable.AlignCenter The same as (Flickable.AlignHCenter | Flickable.AlignVCenter)
2321 \value Flickable.Visible If any part of the child is visible then take no action. Otherwise
2322 move the content item so that the entire child becomes visible.
2323 \value Flickable.Contain If the entire child is visible then take no action. Otherwise
2324 move the content item so that the entire child becomes visible. If the child is
2325 bigger than the view, the top-left part of the child will be preferred.
2326
2327 If no vertical alignment is specified, vertical positioning will be ignored.
2328 The same is true for horizontal alignment.
2329
2330 Optionally, you can specify \a offset to move \e contentX and \e contentY an extra number of
2331 pixels beyond the target alignment.
2332
2333 If positioning the flickable at the child item would cause empty space to be displayed at the
2334 beginning or end of the flickable, the flickable will be positioned at the boundary.
2335
2336 \snippet qml/flickablePositionActiveFocusPosition.qml 0
2337*/
2338
2339void QQuickFlickable::positionViewAtChild(QQuickItem *child, PositionMode mode, const QPointF &offset)
2340{
2341 Q_D(QQuickFlickable);
2342 cancelFlick();
2343
2344 if (!d->contentItem->isAncestorOf(child))
2345 return;
2346
2347 const QRectF itemRect =
2348 child->mapRectToItem(d->contentItem, QRectF(0, 0, child->width(), child->height()));
2349
2350 QPointF currentPosition = QPointF(contentX(), contentY());
2351 QPointF newPosition = computePosition(currentPosition, itemRect, mode, offset);
2352
2353 if (newPosition.x() != currentPosition.x()) {
2354 setContentX(newPosition.x());
2355 d->fixupX();
2356 }
2357 if (newPosition.y() != currentPosition.y()) {
2358 setContentY(newPosition.y());
2359 d->fixupY();
2360 }
2361}
2362
2363/*!
2364 \qmlmethod void QtQuick::Flickable::flickToChild(QQuickItem *child, PositionMode mode, point offset)
2365 \since 6.11
2366
2367 Flicks the flickable such that \a child item (if it is a child) is at the position
2368 specified by \a mode. \a mode can be an or-ed combination of the following:
2369
2370 \value Flickable.AlignLeft Flick the child at the left of the view.
2371 \value Flickable.AlignHCenter Flick the child at the horizontal center of the view.
2372 \value Flickable.AlignRight Flick the child at the right of the view.
2373 \value Flickable.AlignTop Flick the child at the top of the view.
2374 \value Flickable.AlignVCenter Flick the child at the vertical center of the view.
2375 \value Flickable.AlignBottom Flick the child at the bottom of the view.
2376 \value Flickable.AlignCenter The same as (Flickable.AlignHCenter | Flickable.AlignVCenter)
2377 \value Flickable.Visible If any part of the child is visible then take no action. Otherwise
2378 move the content item so that the entire child becomes visible.
2379 \value Flickable.Contain If the entire child is visible then take no action. Otherwise
2380 move the content item so that the entire child becomes visible. If the child is
2381 bigger than the view, the top-left part of the child will be preferred.
2382
2383 If no vertical alignment is specified, vertical flicking will be ignored.
2384 The same is true for horizontal alignment.
2385
2386 Optionally, you can specify \a offset to flick an extra number of
2387 pixels beyond the target alignment.
2388
2389 If flicking the flickable at the child item would cause empty space to be displayed at the
2390 beginning or end of the flickable, the flickable will stop flicking at the boundary.
2391
2392 \snippet qml/flickableFlickActiveFocusPosition.qml 0
2393*/
2394
2395void QQuickFlickable::flickToChild(QQuickItem *child, PositionMode mode, const QPointF &offset)
2396{
2397 Q_D(QQuickFlickable);
2398 cancelFlick();
2399
2400 if (!d->contentItem->isAncestorOf(child))
2401 return;
2402
2403 const QRectF itemRect =
2404 child->mapRectToItem(d->contentItem, QRectF(0, 0, child->width(), child->height()));
2405
2406 QPointF currentPosition = QPointF(contentX(), contentY());
2407 QPointF newPosition = computePosition(currentPosition, itemRect, mode, offset);
2408
2409 flickTo(newPosition);
2410}
2411
2412/*!
2413 \qmlmethod void QtQuick::Flickable::flickTo(point position)
2414 \since 6.11
2415
2416 Flicks the flickable to \a position.
2417
2418 If flicking the flickable would cause empty space to be displayed at the
2419 beginning or end of the flickable, the flickable will stop flicking at the boundary.
2420*/
2421
2422void QQuickFlickable::flickTo(const QPointF &newPosition)
2423{
2424 Q_D(QQuickFlickable);
2425
2426 QPointF currentPosition = QPointF(contentX(), contentY());
2427
2428 qreal xVelocity = 0.0;
2429 qreal yVelocity = 0.0;
2430
2431 const qreal deltaX = newPosition.x() - currentPosition.x();
2432 const qreal deltaY = newPosition.y() - currentPosition.y();
2433
2434 // Calculate velocity based on distance to travel
2435 // Formula: v = sqrt(2 * deceleration * distance)
2436
2437 if (xflick() && qAbs(deltaX) > 0.5) {
2438 const qreal decel = flickDeceleration();
2439 qreal velocity = qSqrt(2.0 * decel * qAbs(deltaX));
2440 if (qAbs(velocity) < _q_MinimumFlickVelocity)
2441 velocity = 0;
2442 const qreal maxVel = maximumFlickVelocity();
2443 if (maxVel > 0 && velocity > maxVel)
2444 velocity = maxVel;
2445 xVelocity = deltaX > 0 ? -velocity : velocity;
2446 }
2447
2448 if (yflick() && qAbs(deltaY) > 0.5) {
2449 const qreal decel = flickDeceleration();
2450 qreal velocity = qSqrt(2.0 * decel * qAbs(deltaY));
2451 if (qAbs(velocity) < _q_MinimumFlickVelocity)
2452 velocity = 0;
2453 const qreal maxVel = maximumFlickVelocity();
2454 if (maxVel > 0 && velocity > maxVel)
2455 velocity = maxVel;
2456 yVelocity = deltaY > 0 ? -velocity : velocity;
2457 }
2458
2459 if (qAbs(xVelocity) > 0.0 || qAbs(yVelocity) > 0.0) {
2460 flick(xVelocity, yVelocity);
2461 } else {
2462 if (newPosition.x() != currentPosition.x()) {
2463 setContentX(newPosition.x());
2464 d->fixupX();
2465 }
2466 if (newPosition.y() != currentPosition.y()) {
2467 setContentY(newPosition.y());
2468 d->fixupY();
2469 }
2470 }
2471}
2472
2473void QQuickFlickablePrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
2474{
2475 if (!prop || !prop->data)
2476 return;
2477
2478 if (QQuickItem *i = qmlobject_cast<QQuickItem *>(o)) {
2479 i->setParentItem(static_cast<QQuickFlickablePrivate*>(prop->data)->contentItem);
2480 } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) {
2481 static_cast<QQuickFlickablePrivate*>(prop->data)->addPointerHandler(pointerHandler);
2482 } else {
2483 o->setParent(prop->object); // XXX todo - do we want this?
2484 }
2485}
2486
2487qsizetype QQuickFlickablePrivate::data_count(QQmlListProperty<QObject> *)
2488{
2489 // XXX todo
2490 return 0;
2491}
2492
2493QObject *QQuickFlickablePrivate::data_at(QQmlListProperty<QObject> *, qsizetype)
2494{
2495 // XXX todo
2496 return nullptr;
2497}
2498
2499void QQuickFlickablePrivate::data_clear(QQmlListProperty<QObject> *)
2500{
2501 // XXX todo
2502}
2503
2504QQmlListProperty<QObject> QQuickFlickable::flickableData()
2505{
2506 Q_D(QQuickFlickable);
2507 return QQmlListProperty<QObject>(this, (void *)d, QQuickFlickablePrivate::data_append,
2508 QQuickFlickablePrivate::data_count,
2509 QQuickFlickablePrivate::data_at,
2510 QQuickFlickablePrivate::data_clear);
2511}
2512
2513QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren()
2514{
2515 Q_D(QQuickFlickable);
2516 return QQuickItemPrivate::get(d->contentItem)->children();
2517}
2518
2519/*!
2520 \qmlproperty enumeration QtQuick::Flickable::boundsBehavior
2521 This property holds whether the surface may be dragged
2522 beyond the Flickable's boundaries, or overshoot the
2523 Flickable's boundaries when flicked.
2524
2525 When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value
2526 other than \c Flickable.StopAtBounds will give a feeling that the edges of
2527 the view are soft, rather than a hard physical boundary.
2528
2529 The \c boundsBehavior can be one of:
2530
2531 \list
2532 \li Flickable.StopAtBounds - the contents can not be dragged beyond the boundary
2533 of the flickable, and flicks will not overshoot.
2534 \li Flickable.DragOverBounds - the contents can be dragged beyond the boundary
2535 of the Flickable, but flicks will not overshoot.
2536 \li Flickable.OvershootBounds - the contents can overshoot the boundary when flicked,
2537 but the content cannot be dragged beyond the boundary of the flickable. (since \c{QtQuick 2.5})
2538 \li Flickable.DragAndOvershootBounds (default) - the contents can be dragged
2539 beyond the boundary of the Flickable, and can overshoot the
2540 boundary when flicked.
2541 \endlist
2542
2543 \sa horizontalOvershoot, verticalOvershoot, boundsMovement
2544*/
2545QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const
2546{
2547 Q_D(const QQuickFlickable);
2548 return d->boundsBehavior;
2549}
2550
2551void QQuickFlickable::setBoundsBehavior(BoundsBehavior b)
2552{
2553 Q_D(QQuickFlickable);
2554 if (b == d->boundsBehavior)
2555 return;
2556 d->boundsBehavior = b;
2557 emit boundsBehaviorChanged();
2558}
2559
2560/*!
2561 \qmlproperty Transition QtQuick::Flickable::rebound
2562
2563 This holds the transition to be applied to the content view when
2564 it snaps back to the bounds of the flickable. The transition is
2565 triggered when the view is flicked or dragged past the edge of the
2566 content area, or when returnToBounds() is called.
2567
2568 \qml
2569 import QtQuick 2.0
2570
2571 Flickable {
2572 width: 150; height: 150
2573 contentWidth: 300; contentHeight: 300
2574
2575 rebound: Transition {
2576 NumberAnimation {
2577 properties: "x,y"
2578 duration: 1000
2579 easing.type: Easing.OutBounce
2580 }
2581 }
2582
2583 Rectangle {
2584 width: 300; height: 300
2585 gradient: Gradient {
2586 GradientStop { position: 0.0; color: "lightsteelblue" }
2587 GradientStop { position: 1.0; color: "blue" }
2588 }
2589 }
2590 }
2591 \endqml
2592
2593 When the above view is flicked beyond its bounds, it will return to its
2594 bounds using the transition specified:
2595
2596 \image flickable-rebound.gif {Flickable content bouncing back
2597 after being dragged beyond its bounds}
2598
2599 If this property is not set, a default animation is applied.
2600 */
2601QQuickTransition *QQuickFlickable::rebound() const
2602{
2603 Q_D(const QQuickFlickable);
2604 return d->rebound;
2605}
2606
2607void QQuickFlickable::setRebound(QQuickTransition *transition)
2608{
2609 Q_D(QQuickFlickable);
2610 if (transition) {
2611 if (!d->hData.transitionToBounds)
2612 d->hData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("x"));
2613 if (!d->vData.transitionToBounds)
2614 d->vData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("y"));
2615 }
2616 if (d->rebound != transition) {
2617 d->rebound = transition;
2618 emit reboundChanged();
2619 }
2620}
2621
2622/*!
2623 \qmlproperty real QtQuick::Flickable::contentWidth
2624 \qmlproperty real QtQuick::Flickable::contentHeight
2625
2626 The dimensions of the content (the surface controlled by Flickable).
2627 This should typically be set to the combined size of the items placed in the
2628 Flickable.
2629
2630 The following snippet shows how these properties are used to display
2631 an image that is larger than the Flickable item itself:
2632
2633 \snippet qml/flickable.qml document
2634
2635 In some cases, the content dimensions can be automatically set
2636 based on the \l {Item::childrenRect.width}{childrenRect.width}
2637 and \l {Item::childrenRect.height}{childrenRect.height} properties
2638 of the \l contentItem. For example, the previous snippet could be rewritten with:
2639
2640 \code
2641 contentWidth: contentItem.childrenRect.width; contentHeight: contentItem.childrenRect.height
2642 \endcode
2643
2644 Though this assumes that the origin of the childrenRect is 0,0.
2645*/
2646qreal QQuickFlickable::contentWidth() const
2647{
2648 Q_D(const QQuickFlickable);
2649 return d->hData.viewSize;
2650}
2651
2652void QQuickFlickable::setContentWidth(qreal w)
2653{
2654 Q_D(QQuickFlickable);
2655 if (d->hData.viewSize == w)
2656 return;
2657 d->hData.viewSize = w;
2658 if (w < 0)
2659 d->contentItem->setWidth(width() - d->hData.startMargin - d->hData.endMargin);
2660 else
2661 d->contentItem->setWidth(w);
2662 d->hData.markExtentsDirty();
2663 // Make sure that we're entirely in view.
2664 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2665 d->fixupMode = QQuickFlickablePrivate::Immediate;
2666 d->fixupX();
2667 } else if (!d->pressed && d->hData.fixingUp) {
2668 d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
2669 d->fixupX();
2670 }
2671 emit contentWidthChanged();
2672 d->updateBeginningEnd();
2673}
2674
2675qreal QQuickFlickable::contentHeight() const
2676{
2677 Q_D(const QQuickFlickable);
2678 return d->vData.viewSize;
2679}
2680
2681void QQuickFlickable::setContentHeight(qreal h)
2682{
2683 Q_D(QQuickFlickable);
2684 if (d->vData.viewSize == h)
2685 return;
2686 d->vData.viewSize = h;
2687 if (h < 0)
2688 d->contentItem->setHeight(height() - d->vData.startMargin - d->vData.endMargin);
2689 else
2690 d->contentItem->setHeight(h);
2691 d->vData.markExtentsDirty();
2692 // Make sure that we're entirely in view.
2693 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2694 d->fixupMode = QQuickFlickablePrivate::Immediate;
2695 d->fixupY();
2696 } else if (!d->pressed && d->vData.fixingUp) {
2697 d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
2698 d->fixupY();
2699 }
2700 emit contentHeightChanged();
2701 d->updateBeginningEnd();
2702}
2703
2704/*!
2705 \qmlproperty real QtQuick::Flickable::topMargin
2706 \qmlproperty real QtQuick::Flickable::leftMargin
2707 \qmlproperty real QtQuick::Flickable::bottomMargin
2708 \qmlproperty real QtQuick::Flickable::rightMargin
2709
2710 These properties hold the margins around the content. This space is reserved
2711 in addition to the contentWidth and contentHeight.
2712*/
2713
2714
2715qreal QQuickFlickable::topMargin() const
2716{
2717 Q_D(const QQuickFlickable);
2718 return d->vData.startMargin;
2719}
2720
2721void QQuickFlickable::setTopMargin(qreal m)
2722{
2723 Q_D(QQuickFlickable);
2724 if (d->vData.startMargin == m)
2725 return;
2726 d->vData.startMargin = m;
2727 d->vData.markExtentsDirty();
2728 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2729 // FIXME: We're not consistently updating the contentY, see QTBUG-131478
2730 d->fixupMode = QQuickFlickablePrivate::Immediate;
2731 d->fixupY();
2732 }
2733 emit topMarginChanged();
2734 d->updateBeginningEnd();
2735}
2736
2737qreal QQuickFlickable::bottomMargin() const
2738{
2739 Q_D(const QQuickFlickable);
2740 return d->vData.endMargin;
2741}
2742
2743void QQuickFlickable::setBottomMargin(qreal m)
2744{
2745 Q_D(QQuickFlickable);
2746 if (d->vData.endMargin == m)
2747 return;
2748 d->vData.endMargin = m;
2749 d->vData.markExtentsDirty();
2750 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2751 // FIXME: We're not consistently updating the contentY, see QTBUG-131478
2752 d->fixupMode = QQuickFlickablePrivate::Immediate;
2753 d->fixupY();
2754 }
2755 emit bottomMarginChanged();
2756 d->updateBeginningEnd();
2757}
2758
2759qreal QQuickFlickable::leftMargin() const
2760{
2761 Q_D(const QQuickFlickable);
2762 return d->hData.startMargin;
2763}
2764
2765void QQuickFlickable::setLeftMargin(qreal m)
2766{
2767 Q_D(QQuickFlickable);
2768 if (d->hData.startMargin == m)
2769 return;
2770 d->hData.startMargin = m;
2771 d->hData.markExtentsDirty();
2772 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2773 // FIXME: We're not consistently updating the contentX, see QTBUG-131478
2774 d->fixupMode = QQuickFlickablePrivate::Immediate;
2775 d->fixupX();
2776 }
2777 emit leftMarginChanged();
2778 d->updateBeginningEnd();
2779}
2780
2781qreal QQuickFlickable::rightMargin() const
2782{
2783 Q_D(const QQuickFlickable);
2784 return d->hData.endMargin;
2785}
2786
2787void QQuickFlickable::setRightMargin(qreal m)
2788{
2789 Q_D(QQuickFlickable);
2790 if (d->hData.endMargin == m)
2791 return;
2792 d->hData.endMargin = m;
2793 d->hData.markExtentsDirty();
2794 if (!d->pressed && !d->hData.moving && !d->vData.moving) {
2795 // FIXME: We're not consistently updating the contentX, see QTBUG-131478
2796 d->fixupMode = QQuickFlickablePrivate::Immediate;
2797 d->fixupX();
2798 }
2799 emit rightMarginChanged();
2800 d->updateBeginningEnd();
2801}
2802
2803/*!
2804 \qmlproperty real QtQuick::Flickable::originX
2805 \qmlproperty real QtQuick::Flickable::originY
2806
2807 These properties hold the origin of the content. This value always refers
2808 to the top-left position of the content regardless of layout direction.
2809
2810 This is usually (0,0), however ListView and GridView may have an arbitrary
2811 origin due to delegate size variation, or item insertion/removal outside
2812 the visible region.
2813
2814 \sa contentX, contentY
2815*/
2816
2817qreal QQuickFlickable::originY() const
2818{
2819 Q_D(const QQuickFlickable);
2820 return -minYExtent() + d->vData.startMargin;
2821}
2822
2823qreal QQuickFlickable::originX() const
2824{
2825 Q_D(const QQuickFlickable);
2826 return -minXExtent() + d->hData.startMargin;
2827}
2828
2829
2830/*!
2831 \qmlmethod void QtQuick::Flickable::resizeContent(real width, real height, point center)
2832
2833 Resizes the content to \a width x \a height about \a center.
2834
2835 This does not scale the contents of the Flickable - it only resizes the \l contentWidth
2836 and \l contentHeight.
2837
2838 Resizing the content may result in the content being positioned outside
2839 the bounds of the Flickable. Calling \l returnToBounds() will
2840 move the content back within legal bounds.
2841*/
2842void QQuickFlickable::resizeContent(qreal w, qreal h, QPointF center)
2843{
2844 Q_D(QQuickFlickable);
2845 const qreal oldHSize = d->hData.viewSize;
2846 const qreal oldVSize = d->vData.viewSize;
2847 const bool needToUpdateWidth = w != oldHSize;
2848 const bool needToUpdateHeight = h != oldVSize;
2849 d->hData.viewSize = w;
2850 d->vData.viewSize = h;
2851 d->contentItem->setSize(QSizeF(w, h));
2852 if (needToUpdateWidth)
2853 emit contentWidthChanged();
2854 if (needToUpdateHeight)
2855 emit contentHeightChanged();
2856
2857 if (center.x() != 0) {
2858 qreal pos = center.x() * w / oldHSize;
2859 setContentX(contentX() + pos - center.x());
2860 }
2861 if (center.y() != 0) {
2862 qreal pos = center.y() * h / oldVSize;
2863 setContentY(contentY() + pos - center.y());
2864 }
2865 d->updateBeginningEnd();
2866}
2867
2868/*!
2869 \qmlmethod void QtQuick::Flickable::returnToBounds()
2870
2871 Ensures the content is within legal bounds.
2872
2873 This may be called to ensure that the content is within legal bounds
2874 after manually positioning the content.
2875*/
2876void QQuickFlickable::returnToBounds()
2877{
2878 Q_D(QQuickFlickable);
2879 d->fixupX();
2880 d->fixupY();
2881}
2882
2883qreal QQuickFlickable::vWidth() const
2884{
2885 Q_D(const QQuickFlickable);
2886 if (d->hData.viewSize < 0)
2887 return width();
2888 else
2889 return d->hData.viewSize;
2890}
2891
2892qreal QQuickFlickable::vHeight() const
2893{
2894 Q_D(const QQuickFlickable);
2895 if (d->vData.viewSize < 0)
2896 return height();
2897 else
2898 return d->vData.viewSize;
2899}
2900
2901/*!
2902 \internal
2903
2904 The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along.
2905
2906 \return true if the flickable is allowed to flick in the horizontal direction, otherwise returns false
2907*/
2908bool QQuickFlickable::xflick() const
2909{
2910 Q_D(const QQuickFlickable);
2911 const int contentWidthWithMargins = d->contentItem->width() + d->hData.startMargin + d->hData.endMargin;
2912 if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentWidthWithMargins > width()))
2913 return true;
2914 if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
2915 return std::floor(qAbs(contentWidthWithMargins - width()));
2916 return d->flickableDirection & QQuickFlickable::HorizontalFlick;
2917}
2918
2919/*!
2920 \internal
2921
2922 The setFlickableDirection function can be used to set constraints on which axis the contentItem can be flicked along.
2923
2924 \return true if the flickable is allowed to flick in the vertical direction, otherwise returns false.
2925*/
2926bool QQuickFlickable::yflick() const
2927{
2928 Q_D(const QQuickFlickable);
2929 const int contentHeightWithMargins = d->contentItem->height() + d->vData.startMargin + d->vData.endMargin;
2930 if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (contentHeightWithMargins > height()))
2931 return true;
2932 if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
2933 return std::floor(qAbs(contentHeightWithMargins - height()));
2934 return d->flickableDirection & QQuickFlickable::VerticalFlick;
2935}
2936
2937QPointF QQuickFlickable::computePosition(QPointF currentPosition, QRectF itemRect, PositionMode mode, const QPointF &offset) const
2938{
2939 QPointF newPosition = currentPosition;
2940
2941 if (xflick()) {
2942 const qreal viewWidth = width();
2943
2944 if (mode & QQuickFlickable::AlignLeft)
2945 newPosition.setX(itemRect.left());
2946 if (mode & QQuickFlickable::AlignHCenter)
2947 newPosition.setX(itemRect.left() - (viewWidth - itemRect.width()) / 2);
2948 if (mode & QQuickFlickable::AlignRight)
2949 newPosition.setX(itemRect.right() - viewWidth);
2950 if (mode & QQuickFlickable::Visible) {
2951 if (itemRect.right() < currentPosition.x())
2952 newPosition.setX(itemRect.left());
2953 else if (itemRect.left() > currentPosition.x() + viewWidth)
2954 newPosition.setX(itemRect.right() - viewWidth);
2955 }
2956 if (mode & QQuickFlickable::Contain) {
2957 if (itemRect.right() > currentPosition.x() + viewWidth)
2958 newPosition.setX(itemRect.right() - viewWidth);
2959 if (itemRect.left() < newPosition.x())
2960 newPosition.setX(itemRect.left());
2961 }
2962
2963 const qreal minX = -minXExtent();
2964 const qreal maxX = -maxXExtent();
2965 newPosition.setX(qMin(newPosition.x(), maxX));
2966 newPosition.setX(qMax(newPosition.x(), minX));
2967 }
2968
2969 if (yflick()) {
2970 const qreal viewHeight = height();
2971
2972 if (mode & QQuickFlickable::AlignTop)
2973 newPosition.setY(itemRect.top());
2974 if (mode & QQuickFlickable::AlignVCenter)
2975 newPosition.setY(itemRect.top() - (viewHeight - itemRect.height()) / 2);
2976 if (mode & QQuickFlickable::AlignBottom)
2977 newPosition.setY(itemRect.bottom() - viewHeight);
2978 if (mode & QQuickFlickable::Visible) {
2979 if (itemRect.bottom() < currentPosition.y())
2980 newPosition.setY(itemRect.top());
2981 else if (itemRect.top() > currentPosition.y() + viewHeight)
2982 newPosition.setY(itemRect.bottom() - viewHeight);
2983 }
2984 if (mode & QQuickFlickable::Contain) {
2985 if (itemRect.bottom() > currentPosition.y() + viewHeight)
2986 newPosition.setY(itemRect.bottom() - viewHeight);
2987 if (itemRect.top() < currentPosition.y())
2988 newPosition.setY(itemRect.top());
2989 }
2990
2991 const qreal minY = -minYExtent();
2992 const qreal maxY = -maxYExtent();
2993 newPosition.setY(qMin(newPosition.y(), maxY));
2994 newPosition.setY(qMax(newPosition.y(), minY));
2995 }
2996
2997 return newPosition + offset;
2998}
2999
3000void QQuickFlickable::mouseUngrabEvent()
3001{
3002 Q_D(QQuickFlickable);
3003 // if our mouse grab has been removed (probably by another Flickable),
3004 // fix our state
3005 if (!d->replayingPressEvent)
3006 d->cancelInteraction();
3007}
3008
3009void QQuickFlickablePrivate::cancelInteraction()
3010{
3011 Q_Q(QQuickFlickable);
3012 if (pressed) {
3013 clearDelayedPress();
3014 pressed = false;
3015 draggingEnding();
3016 stealGrab = false;
3017 q->setKeepMouseGrab(false);
3018 q->setKeepTouchGrab(false);
3019 fixupX();
3020 fixupY();
3021 if (!isViewMoving())
3022 q->movementEnding();
3023 }
3024}
3025
3026void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h)
3027{
3028 Q_Q(const QQuickFlickable);
3029 qCDebug(lcHandlerParent) << "reparenting handler" << h << "to contentItem of" << q;
3030 h->setParent(contentItem);
3031 QQuickItemPrivate::get(contentItem)->addPointerHandler(h);
3032}
3033
3034/*! \internal
3035 QQuickFlickable::filterPointerEvent filters pointer events intercepted on the way
3036 to the child \a receiver, and potentially steals the exclusive grab.
3037
3038 This is how flickable takes over the handling of events from child items.
3039
3040 Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver.
3041*/
3042bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
3043{
3044 Q_D(QQuickFlickable);
3045 const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(event);
3046 const bool isMouse = QQuickDeliveryAgentPrivate::isMouseEvent(event);
3047 if (isMouse || QQuickDeliveryAgentPrivate::isTabletEvent(event)) {
3048 if (!d->buttonsAccepted(static_cast<QSinglePointEvent *>(event)))
3049 return QQuickItem::childMouseEventFilter(receiver, event);
3050 } else if (!isTouch) {
3051 return false; // don't filter hover events or wheel events, for example
3052 }
3053 Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself");
3054 // If a touch event contains a new press point, don't steal right away: watch the movements for a while
3055 if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed))
3056 d->stealGrab = false;
3057 const auto &firstPoint = event->points().first();
3058
3059 if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) {
3060 // We have an exclusive grab (since we're e.g. dragging), but at the same time, we have
3061 // a child with a passive grab (which is why this filter is being called). And because
3062 // of that, we end up getting the same pointer events twice; First in our own event
3063 // handlers (because of the grab), then once more in here, since we filter the child.
3064 // To avoid processing the event twice (e.g. avoid calling handleReleaseEvent once more
3065 // from below), return early. But return false (not true) so that passive-grab handlers
3066 // (TapHandler, DragHandler) inside this Flickable can still receive the event to update
3067 // their state (e.g. setPressed(false) when drag threshold is exceeded).
3068 return false;
3069 }
3070
3071 QPointF localPos = mapFromScene(firstPoint.scenePosition());
3072 bool receiverDisabled = receiver && !receiver->isEnabled();
3073 bool stealThisEvent = d->stealGrab;
3074 bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab());
3075 bool receiverRelinquishGrab = false;
3076
3077 // Special case for MouseArea, try to guess what it does with the event
3078 if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(receiver)) {
3079 bool preventStealing = mouseArea->preventStealing();
3080#if QT_CONFIG(quick_draganddrop)
3081 if (mouseArea->drag() && mouseArea->drag()->target())
3082 preventStealing = true;
3083#endif
3084 if (!preventStealing && receiverKeepsGrab) {
3085 receiverRelinquishGrab = !receiverDisabled || (isMouse
3086 && firstPoint.state() == QEventPoint::State::Pressed
3087 && (receiver->acceptedMouseButtons() & static_cast<QMouseEvent *>(event)->button()));
3088 if (receiverRelinquishGrab)
3089 receiverKeepsGrab = false;
3090 }
3091 }
3092
3093 if ((stealThisEvent || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) {
3094 QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos));
3095 localizedEvent->setAccepted(false);
3096 switch (firstPoint.state()) {
3097 case QEventPoint::State::Updated:
3098 d->handleMoveEvent(localizedEvent.data());
3099 break;
3100 case QEventPoint::State::Pressed:
3101 d->handlePressEvent(localizedEvent.data());
3102 d->captureDelayedPress(receiver, event);
3103 // never grab the pointing device on press during filtering: do it later, during a move
3104 d->stealGrab = false;
3105 stealThisEvent = false;
3106 break;
3107 case QEventPoint::State::Released:
3108 d->handleReleaseEvent(localizedEvent.data());
3109 stealThisEvent = d->stealGrab;
3110 break;
3111 case QEventPoint::State::Stationary:
3112 case QEventPoint::State::Unknown:
3113 break;
3114 }
3115 if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) {
3116 d->clearDelayedPress();
3117 event->setExclusiveGrabber(firstPoint, this);
3118 } else if (d->delayedPressEvent) {
3119 event->setExclusiveGrabber(firstPoint, this);
3120 }
3121 /*
3122 Note that d->stealMouse can be false before the call to d->handleMoveEvent(), but true
3123 afterwards. That means we detected a drag. But even so, we deliberately don't filter
3124 the move event that cause this to happen, since the user might actually be dragging on
3125 a child item, such as a Slider, in which case the child should get a chance to detect
3126 the drag instead, and take the grab. Only if we receive another move event after this,
3127 without the child grabbing the mouse in-between, do we initiate a flick.
3128 An exception is if the user is already in a flicking session started from an earlier
3129 drag, meaning that the content item is still moving (isMoving() == true). In that
3130 case, we see any subsequent drags as being a continuation of the flicking.
3131 Then we filter all events straight away, to avoid triggering any taps or drags in a child.
3132 */
3133 if (isMoving() || (!receiverRelinquishGrab && (stealThisEvent || d->delayedPressEvent || receiverDisabled))) {
3134 // Filter the event
3135 event->setAccepted(true);
3136 return true;
3137 }
3138
3139 // Don't filter the event
3140 return false;
3141 } else if (d->lastPosTime != -1) {
3142 d->lastPosTime = -1;
3143 returnToBounds();
3144 }
3145 if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) {
3146 // mouse released, or another item has claimed the grab
3147 d->lastPosTime = -1;
3148 d->clearDelayedPress();
3149 d->stealGrab = false;
3150 d->pressed = false;
3151 }
3152 return false;
3153}
3154
3155/*! \internal
3156 Despite the name, this function filters all pointer events on their way to any child within.
3157 Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver.
3158*/
3159bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e)
3160{
3161 Q_D(QQuickFlickable);
3162 QPointerEvent *pointerEvent = e->isPointerEvent() ? static_cast<QPointerEvent *>(e) : nullptr;
3163
3164 auto wantsPointerEvent_helper = [this, d, i, pointerEvent]() {
3165 Q_ASSERT(pointerEvent);
3166 QQuickDeliveryAgentPrivate::localizePointerEvent(pointerEvent, this);
3167 const bool wants = d->wantsPointerEvent(pointerEvent);
3168 // re-localize event back to \a i before returning
3169 QQuickDeliveryAgentPrivate::localizePointerEvent(pointerEvent, i);
3170 return wants;
3171 };
3172
3173 if (!isVisible() || !isEnabled() || !isInteractive() ||
3174 (pointerEvent && !wantsPointerEvent_helper())) {
3175 d->cancelInteraction();
3176 return QQuickItem::childMouseEventFilter(i, e);
3177 }
3178
3179 if (e->type() == QEvent::UngrabMouse) {
3180 Q_ASSERT(e->isSinglePointEvent());
3181 auto spe = static_cast<QSinglePointEvent *>(e);
3182 const QObject *grabber = spe->exclusiveGrabber(spe->points().first());
3183 qCDebug(lcFilter) << "filtering UngrabMouse" << spe->points().first() << "for" << i << "grabber is" << grabber;
3184 if (grabber != this)
3185 mouseUngrabEvent(); // A child has been ungrabbed
3186 } else if (pointerEvent) {
3187 return filterPointerEvent(i, pointerEvent);
3188 }
3189
3190 return QQuickItem::childMouseEventFilter(i, e);
3191}
3192
3193/*!
3194 \qmlproperty real QtQuick::Flickable::maximumFlickVelocity
3195 This property holds the maximum velocity that the user can flick the view in pixels/second.
3196
3197 The default value is platform dependent.
3198*/
3199qreal QQuickFlickable::maximumFlickVelocity() const
3200{
3201 Q_D(const QQuickFlickable);
3202 return d->maxVelocity;
3203}
3204
3205void QQuickFlickable::setMaximumFlickVelocity(qreal v)
3206{
3207 Q_D(QQuickFlickable);
3208 if (v == d->maxVelocity)
3209 return;
3210 d->maxVelocity = v;
3211 emit maximumFlickVelocityChanged();
3212}
3213
3214/*!
3215 \qmlproperty real QtQuick::Flickable::flickDeceleration
3216 This property holds the rate at which a flick will decelerate:
3217 the higher the number, the faster it slows down when the user stops
3218 flicking via touch. For example 0.0001 is nearly
3219 "frictionless", and 10000 feels quite "sticky".
3220
3221 The default value is platform dependent. Values of zero or less are not allowed.
3222*/
3223qreal QQuickFlickable::flickDeceleration() const
3224{
3225 Q_D(const QQuickFlickable);
3226 return d->deceleration;
3227}
3228
3229void QQuickFlickable::setFlickDeceleration(qreal deceleration)
3230{
3231 Q_D(QQuickFlickable);
3232 if (deceleration == d->deceleration)
3233 return;
3234 d->deceleration = qMax(0.001, deceleration);
3235 emit flickDecelerationChanged();
3236}
3237
3238bool QQuickFlickable::isFlicking() const
3239{
3240 Q_D(const QQuickFlickable);
3241 return d->hData.flicking || d->vData.flicking;
3242}
3243
3244/*!
3245 \qmlproperty bool QtQuick::Flickable::flicking
3246 \qmlproperty bool QtQuick::Flickable::flickingHorizontally
3247 \qmlproperty bool QtQuick::Flickable::flickingVertically
3248
3249 These properties describe whether the view is currently moving horizontally,
3250 vertically or in either direction, due to the user flicking the view.
3251*/
3252bool QQuickFlickable::isFlickingHorizontally() const
3253{
3254 Q_D(const QQuickFlickable);
3255 return d->hData.flicking;
3256}
3257
3258bool QQuickFlickable::isFlickingVertically() const
3259{
3260 Q_D(const QQuickFlickable);
3261 return d->vData.flicking;
3262}
3263
3264/*!
3265 \qmlproperty bool QtQuick::Flickable::dragging
3266 \qmlproperty bool QtQuick::Flickable::draggingHorizontally
3267 \qmlproperty bool QtQuick::Flickable::draggingVertically
3268
3269 These properties describe whether the view is currently moving horizontally,
3270 vertically or in either direction, due to the user dragging the view.
3271*/
3272bool QQuickFlickable::isDragging() const
3273{
3274 Q_D(const QQuickFlickable);
3275 return d->hData.dragging || d->vData.dragging;
3276}
3277
3278bool QQuickFlickable::isDraggingHorizontally() const
3279{
3280 Q_D(const QQuickFlickable);
3281 return d->hData.dragging;
3282}
3283
3284bool QQuickFlickable::isDraggingVertically() const
3285{
3286 Q_D(const QQuickFlickable);
3287 return d->vData.dragging;
3288}
3289
3290void QQuickFlickablePrivate::draggingStarting()
3291{
3292 Q_Q(QQuickFlickable);
3293 bool wasDragging = hData.dragging || vData.dragging;
3294 if (hMoved && !hData.dragging) {
3295 hData.dragging = true;
3296 emit q->draggingHorizontallyChanged();
3297 }
3298 if (vMoved && !vData.dragging) {
3299 vData.dragging = true;
3300 emit q->draggingVerticallyChanged();
3301 }
3302 if (!wasDragging && (hData.dragging || vData.dragging)) {
3303 emit q->draggingChanged();
3304 emit q->dragStarted();
3305 }
3306}
3307
3308void QQuickFlickablePrivate::draggingEnding()
3309{
3310 Q_Q(QQuickFlickable);
3311 const bool wasDragging = hData.dragging || vData.dragging;
3312 if (hData.dragging) {
3313 hData.dragging = false;
3314 emit q->draggingHorizontallyChanged();
3315 }
3316 if (vData.dragging) {
3317 vData.dragging = false;
3318 emit q->draggingVerticallyChanged();
3319 }
3320 if (wasDragging) {
3321 if (!hData.dragging && !vData.dragging) {
3322 emit q->draggingChanged();
3323 emit q->dragEnded();
3324 }
3325 hData.inRebound = false;
3326 vData.inRebound = false;
3327 }
3328}
3329
3330bool QQuickFlickablePrivate::isViewMoving() const
3331{
3332 if (timeline.isActive()
3333 || (hData.transitionToBounds && hData.transitionToBounds->isActive())
3334 || (vData.transitionToBounds && vData.transitionToBounds->isActive()) ) {
3335 return true;
3336 }
3337 return false;
3338}
3339
3340/*!
3341 \qmlproperty int QtQuick::Flickable::pressDelay
3342
3343 This property holds the time to delay (ms) delivering a press to
3344 children of the Flickable. This can be useful where reacting
3345 to a press before a flicking action has undesirable effects.
3346
3347 If the flickable is dragged/flicked before the delay times out
3348 the press event will not be delivered. If the button is released
3349 within the timeout, both the press and release will be delivered.
3350
3351 Note that for nested Flickables with pressDelay set, the pressDelay of
3352 outer Flickables is overridden by the innermost Flickable. If the drag
3353 exceeds the platform drag threshold, the press event will be delivered
3354 regardless of this property.
3355
3356 \sa QStyleHints
3357*/
3358int QQuickFlickable::pressDelay() const
3359{
3360 Q_D(const QQuickFlickable);
3361 return d->pressDelay;
3362}
3363
3364void QQuickFlickable::setPressDelay(int delay)
3365{
3366 Q_D(QQuickFlickable);
3367 if (d->pressDelay == delay)
3368 return;
3369 d->pressDelay = delay;
3370 emit pressDelayChanged();
3371}
3372
3373/*!
3374 \qmlproperty bool QtQuick::Flickable::moving
3375 \qmlproperty bool QtQuick::Flickable::movingHorizontally
3376 \qmlproperty bool QtQuick::Flickable::movingVertically
3377
3378 These properties describe whether the view is currently moving horizontally,
3379 vertically or in either direction, due to the user either dragging or
3380 flicking the view.
3381*/
3382
3383bool QQuickFlickable::isMoving() const
3384{
3385 Q_D(const QQuickFlickable);
3386 return d->hData.moving || d->vData.moving;
3387}
3388
3389bool QQuickFlickable::isMovingHorizontally() const
3390{
3391 Q_D(const QQuickFlickable);
3392 return d->hData.moving;
3393}
3394
3395bool QQuickFlickable::isMovingVertically() const
3396{
3397 Q_D(const QQuickFlickable);
3398 return d->vData.moving;
3399}
3400
3401void QQuickFlickable::velocityTimelineCompleted()
3402{
3403 Q_D(QQuickFlickable);
3404 if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive())
3405 || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) {
3406 return;
3407 }
3408 // With subclasses such as GridView, velocityTimeline.completed is emitted repeatedly:
3409 // for example setting currentIndex results in a visual "flick" which the user
3410 // didn't initiate directly. We don't want to end movement repeatedly, and in
3411 // that case movementEnding will happen after the sequence of movements ends.
3412 if (d->vData.flicking)
3413 movementEnding();
3414 d->updateBeginningEnd();
3415}
3416
3417void QQuickFlickable::timelineCompleted()
3418{
3419 Q_D(QQuickFlickable);
3420 if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive())
3421 || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) {
3422 return;
3423 }
3424 movementEnding();
3425 d->updateBeginningEnd();
3426}
3427
3428void QQuickFlickable::movementStarting()
3429{
3430 Q_D(QQuickFlickable);
3431 bool wasMoving = d->hData.moving || d->vData.moving;
3432 if (d->hMoved && !d->hData.moving) {
3433 d->hData.moving = true;
3434 emit movingHorizontallyChanged();
3435 }
3436 if (d->vMoved && !d->vData.moving) {
3437 d->vData.moving = true;
3438 emit movingVerticallyChanged();
3439 }
3440
3441 if (!wasMoving && (d->hData.moving || d->vData.moving)) {
3442 emit movingChanged();
3443 emit movementStarted();
3444#if QT_CONFIG(accessibility)
3445 if (QAccessible::isActive()) {
3446 QAccessibleEvent ev(this, QAccessible::ScrollingStart);
3447 QAccessible::updateAccessibility(&ev);
3448 }
3449#endif
3450 }
3451}
3452
3453void QQuickFlickable::movementEnding()
3454{
3455 movementEnding(true, true);
3456}
3457
3458void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding)
3459{
3460 Q_D(QQuickFlickable);
3461
3462 // emit flicking signals
3463 const bool wasFlicking = d->hData.flicking || d->vData.flicking;
3464 if (hMovementEnding && d->hData.flicking) {
3465 d->hData.flicking = false;
3466 emit flickingHorizontallyChanged();
3467 }
3468 if (vMovementEnding && d->vData.flicking) {
3469 d->vData.flicking = false;
3470 emit flickingVerticallyChanged();
3471 }
3472 if (wasFlicking && (!d->hData.flicking || !d->vData.flicking)) {
3473 emit flickingChanged();
3474 emit flickEnded();
3475 } else if (d->hData.flickingWhenDragBegan || d->vData.flickingWhenDragBegan) {
3476 d->hData.flickingWhenDragBegan = !hMovementEnding;
3477 d->vData.flickingWhenDragBegan = !vMovementEnding;
3478 emit flickEnded();
3479 }
3480
3481 // emit moving signals
3482 bool wasMoving = isMoving();
3483 if (hMovementEnding && d->hData.moving
3484 && (!d->pressed && !d->stealGrab)) {
3485 d->hData.moving = false;
3486 d->hMoved = false;
3487 emit movingHorizontallyChanged();
3488 }
3489 if (vMovementEnding && d->vData.moving
3490 && (!d->pressed && !d->stealGrab)) {
3491 d->vData.moving = false;
3492 d->vMoved = false;
3493 emit movingVerticallyChanged();
3494 }
3495 if (wasMoving && !isMoving()) {
3496 emit movingChanged();
3497 emit movementEnded();
3498#if QT_CONFIG(accessibility)
3499 if (QAccessible::isActive()) {
3500 QAccessibleEvent ev(this, QAccessible::ScrollingEnd);
3501 QAccessible::updateAccessibility(&ev);
3502 }
3503#endif
3504 }
3505
3506 if (hMovementEnding) {
3507 d->hData.fixingUp = false;
3508 d->hData.smoothVelocity.setValue(0);
3509 d->hData.previousDragDelta = 0.0;
3510 }
3511 if (vMovementEnding) {
3512 d->vData.fixingUp = false;
3513 d->vData.smoothVelocity.setValue(0);
3514 d->vData.previousDragDelta = 0.0;
3515 }
3516}
3517
3518void QQuickFlickablePrivate::updateVelocity()
3519{
3520 Q_Q(QQuickFlickable);
3521 emit q->horizontalVelocityChanged();
3522 emit q->verticalVelocityChanged();
3523}
3524
3525/*!
3526 \qmlproperty real QtQuick::Flickable::horizontalOvershoot
3527 \since 5.9
3528
3529 This property holds the horizontal overshoot, that is, the horizontal distance by
3530 which the contents has been dragged or flicked past the bounds of the flickable.
3531 The value is negative when the content is dragged or flicked beyond the beginning,
3532 and positive when beyond the end; \c 0.0 otherwise.
3533
3534 Whether the values are reported for dragging and/or flicking is determined by
3535 \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement
3536 is \c Flickable.StopAtBounds.
3537
3538 \sa verticalOvershoot, boundsBehavior, boundsMovement
3539*/
3540qreal QQuickFlickable::horizontalOvershoot() const
3541{
3542 Q_D(const QQuickFlickable);
3543 return d->hData.overshoot;
3544}
3545
3546/*!
3547 \qmlproperty real QtQuick::Flickable::verticalOvershoot
3548 \since 5.9
3549
3550 This property holds the vertical overshoot, that is, the vertical distance by
3551 which the contents has been dragged or flicked past the bounds of the flickable.
3552 The value is negative when the content is dragged or flicked beyond the beginning,
3553 and positive when beyond the end; \c 0.0 otherwise.
3554
3555 Whether the values are reported for dragging and/or flicking is determined by
3556 \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement
3557 is \c Flickable.StopAtBounds.
3558
3559 \sa horizontalOvershoot, boundsBehavior, boundsMovement
3560*/
3561qreal QQuickFlickable::verticalOvershoot() const
3562{
3563 Q_D(const QQuickFlickable);
3564 return d->vData.overshoot;
3565}
3566
3567/*!
3568 \qmlproperty enumeration QtQuick::Flickable::boundsMovement
3569 \since 5.10
3570
3571 This property holds whether the flickable will give a feeling that the edges of the
3572 view are soft, rather than a hard physical boundary.
3573
3574 The \c boundsMovement can be one of:
3575
3576 \list
3577 \li Flickable.StopAtBounds - this allows implementing custom edge effects where the
3578 contents do not follow drags or flicks beyond the bounds of the flickable. The values
3579 of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom
3580 edge effects.
3581 \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or
3582 flicks beyond the bounds of the flickable is determined by \l boundsBehavior.
3583 \endlist
3584
3585 The following example keeps the contents within bounds and instead applies a flip
3586 effect when flicked over horizontal bounds:
3587 \code
3588 Flickable {
3589 id: flickable
3590 boundsMovement: Flickable.StopAtBounds
3591 boundsBehavior: Flickable.DragAndOvershootBounds
3592 transform: Rotation {
3593 axis { x: 0; y: 1; z: 0 }
3594 origin.x: flickable.width / 2
3595 origin.y: flickable.height / 2
3596 angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot))
3597 }
3598 }
3599 \endcode
3600
3601 The following example keeps the contents within bounds and instead applies an opacity
3602 effect when dragged over vertical bounds:
3603 \code
3604 Flickable {
3605 boundsMovement: Flickable.StopAtBounds
3606 boundsBehavior: Flickable.DragOverBounds
3607 opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height)
3608 }
3609 \endcode
3610
3611 \sa boundsBehavior, verticalOvershoot, horizontalOvershoot
3612*/
3613QQuickFlickable::BoundsMovement QQuickFlickable::boundsMovement() const
3614{
3615 Q_D(const QQuickFlickable);
3616 return d->boundsMovement;
3617}
3618
3619void QQuickFlickable::setBoundsMovement(BoundsMovement movement)
3620{
3621 Q_D(QQuickFlickable);
3622 if (d->boundsMovement == movement)
3623 return;
3624
3625 d->boundsMovement = movement;
3626 emit boundsMovementChanged();
3627}
3628
3629QT_END_NAMESPACE
3630
3631#include "moc_qquickflickable_p_p.cpp"
3632
3633#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