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