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