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