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

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