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

source code of qtdeclarative/src/quick/items/qquickflickable.cpp