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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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