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