aboutsummaryrefslogtreecommitdiffstats
path: root/src/declarative/items/qquickflickable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/declarative/items/qquickflickable.cpp')
-rw-r--r--src/declarative/items/qquickflickable.cpp1997
1 files changed, 1997 insertions, 0 deletions
diff --git a/src/declarative/items/qquickflickable.cpp b/src/declarative/items/qquickflickable.cpp
new file mode 100644
index 0000000000..655472004d
--- /dev/null
+++ b/src/declarative/items/qquickflickable.cpp
@@ -0,0 +1,1997 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickflickable_p.h"
+#include "qquickflickable_p_p.h"
+#include "qquickcanvas.h"
+#include "qquickcanvas_p.h"
+
+#include <QtDeclarative/qdeclarativeinfo.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qstylehints.h>
+#include "qplatformdefs.h"
+
+QT_BEGIN_NAMESPACE
+
+// The maximum number of pixels a flick can overshoot
+#ifndef QML_FLICK_OVERSHOOT
+#define QML_FLICK_OVERSHOOT 200
+#endif
+
+// The number of samples to use in calculating the velocity of a flick
+#ifndef QML_FLICK_SAMPLEBUFFER
+#define QML_FLICK_SAMPLEBUFFER 3
+#endif
+
+// The number of samples to discard when calculating the flick velocity.
+// Touch panels often produce inaccurate results as the finger is lifted.
+#ifndef QML_FLICK_DISCARDSAMPLES
+#define QML_FLICK_DISCARDSAMPLES 1
+#endif
+
+// The default maximum velocity of a flick.
+#ifndef QML_FLICK_DEFAULTMAXVELOCITY
+#define QML_FLICK_DEFAULTMAXVELOCITY 2500
+#endif
+
+// The default deceleration of a flick.
+#ifndef QML_FLICK_DEFAULTDECELERATION
+#define QML_FLICK_DEFAULTDECELERATION 1500
+#endif
+
+// How much faster to decelerate when overshooting
+#ifndef QML_FLICK_OVERSHOOTFRICTION
+#define QML_FLICK_OVERSHOOTFRICTION 8
+#endif
+
+// FlickThreshold determines how far the "mouse" must have moved
+// before we perform a flick.
+static const int FlickThreshold = 20;
+
+// RetainGrabVelocity is the maxmimum instantaneous velocity that
+// will ensure the Flickable retains the grab on consecutive flicks.
+static const int RetainGrabVelocity = 15;
+
+QQuickFlickableVisibleArea::QQuickFlickableVisibleArea(QQuickFlickable *parent)
+ : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.)
+ , m_yPosition(0.), m_heightRatio(0.)
+{
+}
+
+qreal QQuickFlickableVisibleArea::widthRatio() const
+{
+ return m_widthRatio;
+}
+
+qreal QQuickFlickableVisibleArea::xPosition() const
+{
+ return m_xPosition;
+}
+
+qreal QQuickFlickableVisibleArea::heightRatio() const
+{
+ return m_heightRatio;
+}
+
+qreal QQuickFlickableVisibleArea::yPosition() const
+{
+ return m_yPosition;
+}
+
+void QQuickFlickableVisibleArea::updateVisible()
+{
+ QQuickFlickablePrivate *p = QQuickFlickablePrivate::get(flickable);
+
+ bool changeX = false;
+ bool changeY = false;
+ bool changeWidth = false;
+ bool changeHeight = false;
+
+ // Vertical
+ const qreal viewheight = flickable->height();
+ const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent();
+ qreal pagePos = (-p->vData.move.value() + flickable->minYExtent()) / (maxyextent + viewheight);
+ qreal pageSize = viewheight / (maxyextent + viewheight);
+
+ if (pageSize != m_heightRatio) {
+ m_heightRatio = pageSize;
+ changeHeight = true;
+ }
+ if (pagePos != m_yPosition) {
+ m_yPosition = pagePos;
+ changeY = true;
+ }
+
+ // Horizontal
+ const qreal viewwidth = flickable->width();
+ const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent();
+ pagePos = (-p->hData.move.value() + flickable->minXExtent()) / (maxxextent + viewwidth);
+ pageSize = viewwidth / (maxxextent + viewwidth);
+
+ if (pageSize != m_widthRatio) {
+ m_widthRatio = pageSize;
+ changeWidth = true;
+ }
+ if (pagePos != m_xPosition) {
+ m_xPosition = pagePos;
+ changeX = true;
+ }
+
+ if (changeX)
+ emit xPositionChanged(m_xPosition);
+ if (changeY)
+ emit yPositionChanged(m_yPosition);
+ if (changeWidth)
+ emit widthRatioChanged(m_widthRatio);
+ if (changeHeight)
+ emit heightRatioChanged(m_heightRatio);
+}
+
+
+QQuickFlickablePrivate::QQuickFlickablePrivate()
+ : contentItem(new QQuickItem)
+ , hData(this, &QQuickFlickablePrivate::setViewportX)
+ , vData(this, &QQuickFlickablePrivate::setViewportY)
+ , hMoved(false), vMoved(false)
+ , stealMouse(false), pressed(false), interactive(true), calcVelocity(false)
+ , pixelAligned(false)
+ , deceleration(QML_FLICK_DEFAULTDECELERATION)
+ , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100)
+ , delayedPressEvent(0), delayedPressTarget(0), pressDelay(0), fixupDuration(400)
+ , fixupMode(Normal), vTime(0), visibleArea(0)
+ , flickableDirection(QQuickFlickable::AutoFlickDirection)
+ , boundsBehavior(QQuickFlickable::DragAndOvershootBounds)
+{
+}
+
+void QQuickFlickablePrivate::init()
+{
+ Q_Q(QQuickFlickable);
+ QDeclarative_setParent_noEvent(contentItem, q);
+ contentItem->setParentItem(q);
+ FAST_CONNECT(&timeline, SIGNAL(updated()), q, SLOT(ticked()))
+ FAST_CONNECT(&timeline, SIGNAL(completed()), q, SLOT(movementEnding()))
+ q->setAcceptedMouseButtons(Qt::LeftButton);
+ q->setFiltersChildMouseEvents(true);
+ QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem);
+ viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry);
+ lastPosTime.invalidate();
+}
+
+/*
+ Returns the amount to overshoot by given a velocity.
+ Will be roughly in range 0 - size/4
+*/
+qreal QQuickFlickablePrivate::overShootDistance(qreal size)
+{
+ if (maxVelocity <= 0)
+ return 0.0;
+
+ return qMin(qreal(QML_FLICK_OVERSHOOT), size/3);
+}
+
+void QQuickFlickablePrivate::AxisData::addVelocitySample(qreal v, qreal maxVelocity)
+{
+ if (v > maxVelocity)
+ v = maxVelocity;
+ else if (v < -maxVelocity)
+ v = -maxVelocity;
+ velocityBuffer.append(v);
+ if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER)
+ velocityBuffer.remove(0);
+}
+
+void QQuickFlickablePrivate::AxisData::updateVelocity()
+{
+ velocity = 0;
+ if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) {
+ int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES;
+ for (int i = 0; i < count; ++i) {
+ qreal v = velocityBuffer.at(i);
+ velocity += v;
+ }
+ velocity /= count;
+ }
+}
+
+void QQuickFlickablePrivate::itemGeometryChanged(QQuickItem *item, const QRectF &newGeom, const QRectF &oldGeom)
+{
+ Q_Q(QQuickFlickable);
+ if (item == contentItem) {
+ if (newGeom.x() != oldGeom.x())
+ emit q->contentXChanged();
+ if (newGeom.y() != oldGeom.y())
+ emit q->contentYChanged();
+ }
+}
+
+void QQuickFlickablePrivate::flickX(qreal velocity)
+{
+ Q_Q(QQuickFlickable);
+ flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity);
+}
+
+void QQuickFlickablePrivate::flickY(qreal velocity)
+{
+ Q_Q(QQuickFlickable);
+ flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity);
+}
+
+void QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal,
+ QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity)
+{
+ Q_Q(QQuickFlickable);
+ qreal maxDistance = -1;
+ data.fixingUp = false;
+ // -ve velocity means list is moving up
+ if (velocity > 0) {
+ maxDistance = qAbs(minExtent - data.move.value());
+ data.flickTarget = minExtent;
+ } else {
+ maxDistance = qAbs(maxExtent - data.move.value());
+ data.flickTarget = maxExtent;
+ }
+ if (maxDistance > 0) {
+ qreal v = velocity;
+ if (maxVelocity != -1 && maxVelocity < qAbs(v)) {
+ if (v < 0)
+ v = -maxVelocity;
+ else
+ v = maxVelocity;
+ }
+ timeline.reset(data.move);
+ if (boundsBehavior == QQuickFlickable::DragAndOvershootBounds)
+ timeline.accel(data.move, v, deceleration);
+ else
+ timeline.accel(data.move, v, deceleration, maxDistance);
+ timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this));
+ if (!hData.flicking && q->xflick()) {
+ hData.flicking = true;
+ emit q->flickingChanged();
+ emit q->flickingHorizontallyChanged();
+ if (!vData.flicking)
+ emit q->flickStarted();
+ }
+ if (!vData.flicking && q->yflick()) {
+ vData.flicking = true;
+ emit q->flickingChanged();
+ emit q->flickingVerticallyChanged();
+ if (!hData.flicking)
+ emit q->flickStarted();
+ }
+ } else {
+ timeline.reset(data.move);
+ fixup(data, minExtent, maxExtent);
+ }
+}
+
+void QQuickFlickablePrivate::fixupY_callback(void *data)
+{
+ ((QQuickFlickablePrivate *)data)->fixupY();
+}
+
+void QQuickFlickablePrivate::fixupX_callback(void *data)
+{
+ ((QQuickFlickablePrivate *)data)->fixupX();
+}
+
+void QQuickFlickablePrivate::fixupX()
+{
+ Q_Q(QQuickFlickable);
+ fixup(hData, q->minXExtent(), q->maxXExtent());
+}
+
+void QQuickFlickablePrivate::fixupY()
+{
+ Q_Q(QQuickFlickable);
+ fixup(vData, q->minYExtent(), q->maxYExtent());
+}
+
+void QQuickFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent)
+{
+ if (data.move.value() > minExtent || maxExtent > minExtent) {
+ timeline.reset(data.move);
+ if (data.move.value() != minExtent) {
+ switch (fixupMode) {
+ case Immediate:
+ timeline.set(data.move, minExtent);
+ break;
+ case ExtentChanged:
+ // The target has changed. Don't start from the beginning; just complete the
+ // second half of the animation using the new extent.
+ timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
+ data.fixingUp = true;
+ break;
+ default: {
+ qreal dist = minExtent - data.move;
+ timeline.move(data.move, minExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4);
+ timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
+ data.fixingUp = true;
+ }
+ }
+ }
+ } else if (data.move.value() < maxExtent) {
+ timeline.reset(data.move);
+ switch (fixupMode) {
+ case Immediate:
+ timeline.set(data.move, maxExtent);
+ break;
+ case ExtentChanged:
+ // The target has changed. Don't start from the beginning; just complete the
+ // second half of the animation using the new extent.
+ timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
+ data.fixingUp = true;
+ break;
+ default: {
+ qreal dist = maxExtent - data.move;
+ timeline.move(data.move, maxExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4);
+ timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4);
+ data.fixingUp = true;
+ }
+ }
+ }
+ data.inOvershoot = false;
+ fixupMode = Normal;
+ vTime = timeline.time();
+}
+
+void QQuickFlickablePrivate::updateBeginningEnd()
+{
+ Q_Q(QQuickFlickable);
+ bool atBoundaryChange = false;
+
+ // Vertical
+ const int maxyextent = int(-q->maxYExtent());
+ const qreal ypos = -vData.move.value();
+ bool atBeginning = (ypos <= -q->minYExtent());
+ bool atEnd = (maxyextent <= ypos);
+
+ if (atBeginning != vData.atBeginning) {
+ vData.atBeginning = atBeginning;
+ atBoundaryChange = true;
+ }
+ if (atEnd != vData.atEnd) {
+ vData.atEnd = atEnd;
+ atBoundaryChange = true;
+ }
+
+ // Horizontal
+ const int maxxextent = int(-q->maxXExtent());
+ const qreal xpos = -hData.move.value();
+ atBeginning = (xpos <= -q->minXExtent());
+ atEnd = (maxxextent <= xpos);
+
+ if (atBeginning != hData.atBeginning) {
+ hData.atBeginning = atBeginning;
+ atBoundaryChange = true;
+ }
+ if (atEnd != hData.atEnd) {
+ hData.atEnd = atEnd;
+ atBoundaryChange = true;
+ }
+
+ if (vData.extentsChanged) {
+ vData.extentsChanged = false;
+ emit q->yOriginChanged();
+ }
+
+ if (hData.extentsChanged) {
+ hData.extentsChanged = false;
+ emit q->xOriginChanged();
+ }
+
+ if (atBoundaryChange)
+ emit q->isAtBoundaryChanged();
+
+ if (visibleArea)
+ visibleArea->updateVisible();
+}
+
+/*
+XXXTODO add docs describing moving, dragging, flicking properties, e.g.
+
+When the user starts dragging the Flickable, the dragging and moving properties
+will be true.
+
+If the velocity is sufficient when the drag is ended, flicking may begin.
+
+The moving properties will remain true until all dragging and flicking
+is finished.
+*/
+
+/*!
+ \qmlsignal QtQuick2::Flickable::onDragStarted()
+
+ This handler is called when the view starts to be dragged due to user
+ interaction.
+*/
+
+/*!
+ \qmlsignal QtQuick2::Flickable::onDragEnded()
+
+ This handler is called when the user stops dragging the view.
+
+ If the velocity of the drag is suffient at the time the
+ touch/mouse button is released then a flick will start.
+*/
+
+/*!
+ \qmlclass Flickable QQuickFlickable
+ \inqmlmodule QtQuick 2
+ \ingroup qml-basic-interaction-elements
+
+ \brief The Flickable item provides a surface that can be "flicked".
+ \inherits Item
+
+ The Flickable item places its children on a surface that can be dragged
+ and flicked, causing the view onto the child items to scroll. This
+ behavior forms the basis of Items that are designed to show large numbers
+ of child items, such as \l ListView and \l GridView.
+
+ In traditional user interfaces, views can be scrolled using standard
+ controls, such as scroll bars and arrow buttons. In some situations, it
+ is also possible to drag the view directly by pressing and holding a
+ mouse button while moving the cursor. In touch-based user interfaces,
+ this dragging action is often complemented with a flicking action, where
+ scrolling continues after the user has stopped touching the view.
+
+ Flickable does not automatically clip its contents. If it is not used as
+ a full-screen item, you should consider setting the \l{Item::}{clip} property
+ to true.
+
+ \section1 Example Usage
+
+ \div {class="float-right"}
+ \inlineimage flickable.gif
+ \enddiv
+
+ The following example shows a small view onto a large image in which the
+ user can drag or flick the image in order to view different parts of it.
+
+ \snippet doc/src/snippets/declarative/flickable.qml document
+
+ \clearfloat
+
+ Items declared as children of a Flickable are automatically parented to the
+ Flickable's \l contentItem. This should be taken into account when
+ operating on the children of the Flickable; it is usually the children of
+ \c contentItem that are relevant. For example, the bound of Items added
+ to the Flickable will be available by \c contentItem.childrenRect
+
+ \section1 Limitations
+
+ \note Due to an implementation detail, items placed inside a Flickable cannot anchor to it by
+ \c id. Use \c parent instead.
+*/
+
+/*!
+ \qmlsignal QtQuick2::Flickable::onMovementStarted()
+
+ This handler is called when the view begins moving due to user
+ interaction.
+*/
+
+/*!
+ \qmlsignal QtQuick2::Flickable::onMovementEnded()
+
+ This handler is called when the view stops moving due to user
+ interaction. If a flick was generated, this handler will
+ be triggered once the flick stops. If a flick was not
+ generated, the handler will be triggered when the
+ user stops dragging - i.e. a mouse or touch release.
+*/
+
+/*!
+ \qmlsignal QtQuick2::Flickable::onFlickStarted()
+
+ This handler is called when the view is flicked. A flick
+ starts from the point that the mouse or touch is released,
+ while still in motion.
+*/
+
+/*!
+ \qmlsignal QtQuick2::Flickable::onFlickEnded()
+
+ This handler is called when the view stops moving due to a flick.
+*/
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::visibleArea.xPosition
+ \qmlproperty real QtQuick2::Flickable::visibleArea.widthRatio
+ \qmlproperty real QtQuick2::Flickable::visibleArea.yPosition
+ \qmlproperty real QtQuick2::Flickable::visibleArea.heightRatio
+
+ These properties describe the position and size of the currently viewed area.
+ The size is defined as the percentage of the full view currently visible,
+ scaled to 0.0 - 1.0. The page position is usually in the range 0.0 (beginning) to
+ 1.0 minus size ratio (end), i.e. \c yPosition is in the range 0.0 to 1.0-\c heightRatio.
+ However, it is possible for the contents to be dragged outside of the normal
+ range, resulting in the page positions also being outside the normal range.
+
+ These properties are typically used to draw a scrollbar. For example:
+
+ \snippet doc/src/snippets/declarative/flickableScrollbar.qml 0
+ \dots 8
+ \snippet doc/src/snippets/declarative/flickableScrollbar.qml 1
+
+ \sa {declarative/ui-components/scrollbar}{scrollbar example}
+*/
+QQuickFlickable::QQuickFlickable(QQuickItem *parent)
+ : QQuickItem(*(new QQuickFlickablePrivate), parent)
+{
+ Q_D(QQuickFlickable);
+ d->init();
+}
+
+QQuickFlickable::QQuickFlickable(QQuickFlickablePrivate &dd, QQuickItem *parent)
+ : QQuickItem(dd, parent)
+{
+ Q_D(QQuickFlickable);
+ d->init();
+}
+
+QQuickFlickable::~QQuickFlickable()
+{
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::contentX
+ \qmlproperty real QtQuick2::Flickable::contentY
+
+ These properties hold the surface coordinate currently at the top-left
+ corner of the Flickable. For example, if you flick an image up 100 pixels,
+ \c contentY will be 100.
+*/
+qreal QQuickFlickable::contentX() const
+{
+ Q_D(const QQuickFlickable);
+ return -d->contentItem->x();
+}
+
+void QQuickFlickable::setContentX(qreal pos)
+{
+ Q_D(QQuickFlickable);
+ d->hData.explicitValue = true;
+ d->timeline.reset(d->hData.move);
+ d->vTime = d->timeline.time();
+ movementXEnding();
+ if (-pos != d->hData.move.value()) {
+ d->hData.move.setValue(-pos);
+ viewportMoved();
+ }
+}
+
+qreal QQuickFlickable::contentY() const
+{
+ Q_D(const QQuickFlickable);
+ return -d->contentItem->y();
+}
+
+void QQuickFlickable::setContentY(qreal pos)
+{
+ Q_D(QQuickFlickable);
+ d->vData.explicitValue = true;
+ d->timeline.reset(d->vData.move);
+ d->vTime = d->timeline.time();
+ movementYEnding();
+ if (-pos != d->vData.move.value()) {
+ d->vData.move.setValue(-pos);
+ viewportMoved();
+ }
+}
+
+/*!
+ \qmlproperty bool QtQuick2::Flickable::interactive
+
+ This property describes whether the user can interact with the Flickable.
+ A user cannot drag or flick a Flickable that is not interactive.
+
+ By default, this property is true.
+
+ This property is useful for temporarily disabling flicking. This allows
+ special interaction with Flickable's children; for example, you might want
+ to freeze a flickable map while scrolling through a pop-up dialog that
+ is a child of the Flickable.
+*/
+bool QQuickFlickable::isInteractive() const
+{
+ Q_D(const QQuickFlickable);
+ return d->interactive;
+}
+
+void QQuickFlickable::setInteractive(bool interactive)
+{
+ Q_D(QQuickFlickable);
+ if (interactive != d->interactive) {
+ d->interactive = interactive;
+ if (!interactive && (d->hData.flicking || d->vData.flicking)) {
+ d->timeline.clear();
+ d->vTime = d->timeline.time();
+ d->hData.flicking = false;
+ d->vData.flicking = false;
+ emit flickingChanged();
+ emit flickingHorizontallyChanged();
+ emit flickingVerticallyChanged();
+ emit flickEnded();
+ }
+ emit interactiveChanged();
+ }
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::horizontalVelocity
+ \qmlproperty real QtQuick2::Flickable::verticalVelocity
+
+ The instantaneous velocity of movement along the x and y axes, in pixels/sec.
+
+ The reported velocity is smoothed to avoid erratic output.
+*/
+qreal QQuickFlickable::horizontalVelocity() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.smoothVelocity.value();
+}
+
+qreal QQuickFlickable::verticalVelocity() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.smoothVelocity.value();
+}
+
+/*!
+ \qmlproperty bool QtQuick2::Flickable::atXBeginning
+ \qmlproperty bool QtQuick2::Flickable::atXEnd
+ \qmlproperty bool QtQuick2::Flickable::atYBeginning
+ \qmlproperty bool QtQuick2::Flickable::atYEnd
+
+ These properties are true if the flickable view is positioned at the beginning,
+ or end respecively.
+*/
+bool QQuickFlickable::isAtXEnd() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.atEnd;
+}
+
+bool QQuickFlickable::isAtXBeginning() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.atBeginning;
+}
+
+bool QQuickFlickable::isAtYEnd() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.atEnd;
+}
+
+bool QQuickFlickable::isAtYBeginning() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.atBeginning;
+}
+
+void QQuickFlickable::ticked()
+{
+ viewportMoved();
+}
+
+/*!
+ \qmlproperty Item QtQuick2::Flickable::contentItem
+
+ The internal item that contains the Items to be moved in the Flickable.
+
+ Items declared as children of a Flickable are automatically parented to the Flickable's contentItem.
+
+ Items created dynamically need to be explicitly parented to the \e contentItem:
+ \code
+ Flickable {
+ id: myFlickable
+ function addItem(file) {
+ var component = Qt.createComponent(file)
+ component.createObject(myFlickable.contentItem);
+ }
+ }
+ \endcode
+*/
+QQuickItem *QQuickFlickable::contentItem()
+{
+ Q_D(QQuickFlickable);
+ return d->contentItem;
+}
+
+QQuickFlickableVisibleArea *QQuickFlickable::visibleArea()
+{
+ Q_D(QQuickFlickable);
+ if (!d->visibleArea)
+ d->visibleArea = new QQuickFlickableVisibleArea(this);
+ return d->visibleArea;
+}
+
+/*!
+ \qmlproperty enumeration QtQuick2::Flickable::flickableDirection
+
+ This property determines which directions the view can be flicked.
+
+ \list
+ \o Flickable.AutoFlickDirection (default) - allows flicking vertically if the
+ \e contentHeight is not equal to the \e height of the Flickable.
+ Allows flicking horizontally if the \e contentWidth is not equal
+ to the \e width of the Flickable.
+ \o Flickable.HorizontalFlick - allows flicking horizontally.
+ \o Flickable.VerticalFlick - allows flicking vertically.
+ \o Flickable.HorizontalAndVerticalFlick - allows flicking in both directions.
+ \endlist
+*/
+QQuickFlickable::FlickableDirection QQuickFlickable::flickableDirection() const
+{
+ Q_D(const QQuickFlickable);
+ return d->flickableDirection;
+}
+
+void QQuickFlickable::setFlickableDirection(FlickableDirection direction)
+{
+ Q_D(QQuickFlickable);
+ if (direction != d->flickableDirection) {
+ d->flickableDirection = direction;
+ emit flickableDirectionChanged();
+ }
+}
+
+bool QQuickFlickable::pixelAligned() const
+{
+ Q_D(const QQuickFlickable);
+ return d->pixelAligned;
+}
+
+void QQuickFlickable::setPixelAligned(bool align)
+{
+ Q_D(QQuickFlickable);
+ if (align != d->pixelAligned) {
+ d->pixelAligned = align;
+ emit pixelAlignedChanged();
+ }
+}
+
+void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event)
+{
+ Q_Q(QQuickFlickable);
+ if (interactive && timeline.isActive()
+ && (qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity
+ || qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity)) {
+ stealMouse = true; // If we've been flicked then steal the click.
+ } else {
+ stealMouse = false;
+ }
+ q->setKeepMouseGrab(stealMouse);
+ pressed = true;
+ timeline.clear();
+ hData.reset();
+ vData.reset();
+ hData.dragMinBound = q->minXExtent();
+ vData.dragMinBound = q->minYExtent();
+ hData.dragMaxBound = q->maxXExtent();
+ vData.dragMaxBound = q->maxYExtent();
+ fixupMode = Normal;
+ lastPos = QPoint();
+ QQuickItemPrivate::start(lastPosTime);
+ pressPos = event->localPos();
+ hData.pressPos = hData.move.value();
+ vData.pressPos = vData.move.value();
+ hData.flicking = false;
+ vData.flicking = false;
+ QQuickItemPrivate::start(pressTime);
+ QQuickItemPrivate::start(velocityTime);
+}
+
+void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event)
+{
+ Q_Q(QQuickFlickable);
+ if (!interactive || !lastPosTime.isValid())
+ return;
+ bool rejectY = false;
+ bool rejectX = false;
+
+ bool stealY = stealMouse;
+ bool stealX = stealMouse;
+
+ if (q->yflick()) {
+ int dy = int(event->localPos().y() - pressPos.y());
+ if (qAbs(dy) > qApp->styleHints()->startDragDistance() || QQuickItemPrivate::elapsed(pressTime) > 200) {
+ if (!vMoved)
+ vData.dragStartOffset = dy;
+ qreal newY = dy + vData.pressPos - vData.dragStartOffset;
+ const qreal minY = vData.dragMinBound;
+ const qreal maxY = vData.dragMaxBound;
+ if (newY > minY)
+ newY = minY + (newY - minY) / 2;
+ if (newY < maxY && maxY - minY <= 0)
+ newY = maxY + (newY - maxY) / 2;
+ if (boundsBehavior == QQuickFlickable::StopAtBounds && (newY > minY || newY < maxY)) {
+ rejectY = true;
+ if (newY < maxY) {
+ newY = maxY;
+ rejectY = false;
+ }
+ if (newY > minY) {
+ newY = minY;
+ rejectY = false;
+ }
+ }
+ if (!rejectY && stealMouse) {
+ vData.move.setValue(qRound(newY));
+ vMoved = true;
+ }
+ if (qAbs(dy) > qApp->styleHints()->startDragDistance())
+ stealY = true;
+ }
+ }
+
+ if (q->xflick()) {
+ int dx = int(event->localPos().x() - pressPos.x());
+ if (qAbs(dx) > qApp->styleHints()->startDragDistance() || QQuickItemPrivate::elapsed(pressTime) > 200) {
+ if (!hMoved)
+ hData.dragStartOffset = dx;
+ qreal newX = dx + hData.pressPos - hData.dragStartOffset;
+ const qreal minX = hData.dragMinBound;
+ const qreal maxX = hData.dragMaxBound;
+ if (newX > minX)
+ newX = minX + (newX - minX) / 2;
+ if (newX < maxX && maxX - minX <= 0)
+ newX = maxX + (newX - maxX) / 2;
+ if (boundsBehavior == QQuickFlickable::StopAtBounds && (newX > minX || newX < maxX)) {
+ rejectX = true;
+ if (newX < maxX) {
+ newX = maxX;
+ rejectX = false;
+ }
+ if (newX > minX) {
+ newX = minX;
+ rejectX = false;
+ }
+ }
+ if (!rejectX && stealMouse) {
+ hData.move.setValue(qRound(newX));
+ hMoved = true;
+ }
+
+ if (qAbs(dx) > qApp->styleHints()->startDragDistance())
+ stealX = true;
+ }
+ }
+
+ stealMouse = stealX || stealY;
+ if (stealMouse)
+ q->setKeepMouseGrab(true);
+
+ if (rejectY) {
+ vData.velocityBuffer.clear();
+ vData.velocity = 0;
+ }
+ if (rejectX) {
+ hData.velocityBuffer.clear();
+ hData.velocity = 0;
+ }
+
+ if (hMoved || vMoved) {
+ draggingStarting();
+ q->movementStarting();
+ q->viewportMoved();
+ }
+
+ if (!lastPos.isNull()) {
+ qreal elapsed = qreal(QQuickItemPrivate::elapsed(lastPosTime)) / 1000.;
+ if (elapsed <= 0)
+ return;
+ QQuickItemPrivate::restart(lastPosTime);
+ qreal dy = event->localPos().y()-lastPos.y();
+ if (q->yflick() && !rejectY)
+ vData.addVelocitySample(dy/elapsed, maxVelocity);
+ qreal dx = event->localPos().x()-lastPos.x();
+ if (q->xflick() && !rejectX)
+ hData.addVelocitySample(dx/elapsed, maxVelocity);
+ }
+
+ lastPos = event->localPos();
+}
+
+void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event)
+{
+ Q_Q(QQuickFlickable);
+ stealMouse = false;
+ q->setKeepMouseGrab(false);
+ pressed = false;
+
+ // if we drag then pause before release we should not cause a flick.
+ qint64 elapsed = QQuickItemPrivate::elapsed(lastPosTime);
+
+ vData.updateVelocity();
+ hData.updateVelocity();
+
+ draggingEnding();
+
+ if (!lastPosTime.isValid())
+ return;
+
+ vTime = timeline.time();
+
+ qreal velocity = elapsed < 100 ? vData.velocity : 0;
+ if (vData.atBeginning || vData.atEnd)
+ velocity /= 2;
+ if (q->yflick() && qAbs(velocity) > MinimumFlickVelocity && qAbs(event->localPos().y() - pressPos.y()) > FlickThreshold) {
+ velocityTimeline.reset(vData.smoothVelocity);
+ vData.smoothVelocity.setValue(-velocity);
+ flickY(velocity);
+ } else {
+ fixupY();
+ }
+
+ velocity = elapsed < 100 ? hData.velocity : 0;
+ if (hData.atBeginning || hData.atEnd)
+ velocity /= 2;
+ if (q->xflick() && qAbs(velocity) > MinimumFlickVelocity && qAbs(event->localPos().x() - pressPos.x()) > FlickThreshold) {
+ velocityTimeline.reset(hData.smoothVelocity);
+ hData.smoothVelocity.setValue(-velocity);
+ flickX(velocity);
+ } else {
+ fixupX();
+ }
+
+ if (!timeline.isActive())
+ q->movementEnding();
+}
+
+void QQuickFlickable::mousePressEvent(QMouseEvent *event)
+{
+ Q_D(QQuickFlickable);
+ if (d->interactive) {
+ if (!d->pressed)
+ d->handleMousePressEvent(event);
+ event->accept();
+ } else {
+ QQuickItem::mousePressEvent(event);
+ }
+}
+
+void QQuickFlickable::mouseMoveEvent(QMouseEvent *event)
+{
+ Q_D(QQuickFlickable);
+ if (d->interactive) {
+ d->handleMouseMoveEvent(event);
+ event->accept();
+ } else {
+ QQuickItem::mouseMoveEvent(event);
+ }
+}
+
+void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event)
+{
+ Q_D(QQuickFlickable);
+ if (d->interactive) {
+ d->clearDelayedPress();
+ d->handleMouseReleaseEvent(event);
+ event->accept();
+ ungrabMouse();
+ } else {
+ QQuickItem::mouseReleaseEvent(event);
+ }
+}
+
+void QQuickFlickable::wheelEvent(QWheelEvent *event)
+{
+ Q_D(QQuickFlickable);
+ if (!d->interactive) {
+ QQuickItem::wheelEvent(event);
+ } else if (yflick() && event->orientation() == Qt::Vertical) {
+ bool valid = false;
+ if (event->delta() > 0 && contentY() > -minYExtent()) {
+ d->vData.velocity = qMax(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(d->maxVelocity/4));
+ valid = true;
+ } else if (event->delta() < 0 && contentY() < -maxYExtent()) {
+ d->vData.velocity = qMin(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(-d->maxVelocity/4));
+ valid = true;
+ }
+ if (valid) {
+ d->vData.flicking = false;
+ d->flickY(d->vData.velocity);
+ if (d->vData.flicking) {
+ d->vMoved = true;
+ movementStarting();
+ }
+ event->accept();
+ }
+ } else if (xflick() && event->orientation() == Qt::Horizontal) {
+ bool valid = false;
+ if (event->delta() > 0 && contentX() > -minXExtent()) {
+ d->hData.velocity = qMax(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(d->maxVelocity/4));
+ valid = true;
+ } else if (event->delta() < 0 && contentX() < -maxXExtent()) {
+ d->hData.velocity = qMin(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(-d->maxVelocity/4));
+ valid = true;
+ }
+ if (valid) {
+ d->hData.flicking = false;
+ d->flickX(d->hData.velocity);
+ if (d->hData.flicking) {
+ d->hMoved = true;
+ movementStarting();
+ }
+ event->accept();
+ }
+ } else {
+ QQuickItem::wheelEvent(event);
+ }
+}
+
+bool QQuickFlickablePrivate::isOutermostPressDelay() const
+{
+ Q_Q(const QQuickFlickable);
+ QQuickItem *item = q->parentItem();
+ while (item) {
+ QQuickFlickable *flick = qobject_cast<QQuickFlickable*>(item);
+ if (flick && flick->pressDelay() > 0 && flick->isInteractive())
+ return false;
+ item = item->parentItem();
+ }
+
+ return true;
+}
+
+void QQuickFlickablePrivate::captureDelayedPress(QMouseEvent *event)
+{
+ Q_Q(QQuickFlickable);
+ if (!q->canvas() || pressDelay <= 0)
+ return;
+ if (!isOutermostPressDelay())
+ return;
+ delayedPressTarget = q->canvas()->mouseGrabberItem();
+ delayedPressEvent = new QMouseEvent(*event);
+ delayedPressEvent->setAccepted(false);
+ delayedPressTimer.start(pressDelay, q);
+}
+
+void QQuickFlickablePrivate::clearDelayedPress()
+{
+ if (delayedPressEvent) {
+ delayedPressTimer.stop();
+ delete delayedPressEvent;
+ delayedPressEvent = 0;
+ }
+}
+
+//XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned.
+void QQuickFlickablePrivate::setViewportX(qreal x)
+{
+ contentItem->setX(pixelAligned ? qRound(x) : x);
+}
+
+void QQuickFlickablePrivate::setViewportY(qreal y)
+{
+ contentItem->setY(pixelAligned ? qRound(y) : y);
+}
+
+void QQuickFlickable::timerEvent(QTimerEvent *event)
+{
+ Q_D(QQuickFlickable);
+ if (event->timerId() == d->delayedPressTimer.timerId()) {
+ d->delayedPressTimer.stop();
+ if (d->delayedPressEvent) {
+ QQuickItem *grabber = canvas() ? canvas()->mouseGrabberItem() : 0;
+ if (!grabber || grabber != this) {
+ // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay)
+ // so we reset the grabber
+ if (canvas()->mouseGrabberItem() == d->delayedPressTarget)
+ d->delayedPressTarget->ungrabMouse();
+ // Use the event handler that will take care of finding the proper item to propagate the event
+ QQuickCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent);
+ }
+ delete d->delayedPressEvent;
+ d->delayedPressEvent = 0;
+ }
+ }
+}
+
+qreal QQuickFlickable::minYExtent() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.startMargin;
+}
+
+qreal QQuickFlickable::minXExtent() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.startMargin;
+}
+
+/* returns -ve */
+qreal QQuickFlickable::maxXExtent() const
+{
+ Q_D(const QQuickFlickable);
+ return width() - vWidth() - d->hData.endMargin;
+}
+/* returns -ve */
+qreal QQuickFlickable::maxYExtent() const
+{
+ Q_D(const QQuickFlickable);
+ return height() - vHeight() - d->vData.endMargin;
+}
+
+void QQuickFlickable::componentComplete()
+{
+ Q_D(QQuickFlickable);
+ QQuickItem::componentComplete();
+ if (!d->hData.explicitValue && d->hData.startMargin != 0.)
+ setContentX(-minXExtent());
+ if (!d->vData.explicitValue && d->vData.startMargin != 0.)
+ setContentY(-minYExtent());
+}
+
+void QQuickFlickable::viewportMoved()
+{
+ Q_D(QQuickFlickable);
+
+ qreal prevX = d->lastFlickablePosition.x();
+ qreal prevY = d->lastFlickablePosition.y();
+ if (d->pressed || d->calcVelocity) {
+ int elapsed = QQuickItemPrivate::restart(d->velocityTime);
+ if (elapsed > 0) {
+ qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / elapsed;
+ if (qAbs(horizontalVelocity) > 0) {
+ d->velocityTimeline.reset(d->hData.smoothVelocity);
+ d->velocityTimeline.move(d->hData.smoothVelocity, horizontalVelocity, d->reportedVelocitySmoothing);
+ d->velocityTimeline.move(d->hData.smoothVelocity, 0, d->reportedVelocitySmoothing);
+ }
+ qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / elapsed;
+ if (qAbs(verticalVelocity) > 0) {
+ d->velocityTimeline.reset(d->vData.smoothVelocity);
+ d->velocityTimeline.move(d->vData.smoothVelocity, verticalVelocity, d->reportedVelocitySmoothing);
+ d->velocityTimeline.move(d->vData.smoothVelocity, 0, d->reportedVelocitySmoothing);
+ }
+ }
+ } else {
+ if (d->timeline.time() > d->vTime) {
+ d->velocityTimeline.clear();
+ qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / (d->timeline.time() - d->vTime);
+ qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / (d->timeline.time() - d->vTime);
+ d->hData.smoothVelocity.setValue(horizontalVelocity);
+ d->vData.smoothVelocity.setValue(verticalVelocity);
+ }
+ }
+
+ if (!d->vData.inOvershoot && !d->vData.fixingUp && d->vData.flicking
+ && (d->vData.move.value() > minYExtent() || d->vData.move.value() < maxYExtent())
+ && qAbs(d->vData.smoothVelocity.value()) > 100) {
+ // Increase deceleration if we've passed a bound
+ d->vData.inOvershoot = true;
+ qreal maxDistance = d->overShootDistance(height());
+ d->timeline.reset(d->vData.move);
+ d->timeline.accel(d->vData.move, -d->vData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
+ d->timeline.callback(QDeclarativeTimeLineCallback(&d->vData.move, d->fixupY_callback, d));
+ }
+ if (!d->hData.inOvershoot && !d->hData.fixingUp && d->hData.flicking
+ && (d->hData.move.value() > minXExtent() || d->hData.move.value() < maxXExtent())
+ && qAbs(d->hData.smoothVelocity.value()) > 100) {
+ // Increase deceleration if we've passed a bound
+ d->hData.inOvershoot = true;
+ qreal maxDistance = d->overShootDistance(width());
+ d->timeline.reset(d->hData.move);
+ d->timeline.accel(d->hData.move, -d->hData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance);
+ d->timeline.callback(QDeclarativeTimeLineCallback(&d->hData.move, d->fixupX_callback, d));
+ }
+
+ d->lastFlickablePosition = QPointF(d->hData.move.value(), d->vData.move.value());
+
+ d->vTime = d->timeline.time();
+ d->updateBeginningEnd();
+}
+
+void QQuickFlickable::geometryChanged(const QRectF &newGeometry,
+ const QRectF &oldGeometry)
+{
+ Q_D(QQuickFlickable);
+ QQuickItem::geometryChanged(newGeometry, oldGeometry);
+
+ bool changed = false;
+ if (newGeometry.width() != oldGeometry.width()) {
+ if (xflick())
+ changed = true;
+ if (d->hData.viewSize < 0) {
+ d->contentItem->setWidth(width());
+ emit contentWidthChanged();
+ }
+ // Make sure that we're entirely in view.
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupX();
+ }
+ }
+ if (newGeometry.height() != oldGeometry.height()) {
+ if (yflick())
+ changed = true;
+ if (d->vData.viewSize < 0) {
+ d->contentItem->setHeight(height());
+ emit contentHeightChanged();
+ }
+ // Make sure that we're entirely in view.
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupY();
+ }
+ }
+
+ if (changed)
+ d->updateBeginningEnd();
+}
+
+void QQuickFlickable::cancelFlick()
+{
+ Q_D(QQuickFlickable);
+ d->timeline.reset(d->hData.move);
+ d->timeline.reset(d->vData.move);
+ movementEnding();
+}
+
+void QQuickFlickablePrivate::data_append(QDeclarativeListProperty<QObject> *prop, QObject *o)
+{
+ QQuickItem *i = qobject_cast<QQuickItem *>(o);
+ if (i) {
+ i->setParentItem(static_cast<QQuickFlickablePrivate*>(prop->data)->contentItem);
+ } else {
+ o->setParent(prop->object); // XXX todo - do we want this?
+ }
+}
+
+int QQuickFlickablePrivate::data_count(QDeclarativeListProperty<QObject> *)
+{
+ // XXX todo
+ return 0;
+}
+
+QObject *QQuickFlickablePrivate::data_at(QDeclarativeListProperty<QObject> *, int)
+{
+ // XXX todo
+ return 0;
+}
+
+void QQuickFlickablePrivate::data_clear(QDeclarativeListProperty<QObject> *)
+{
+ // XXX todo
+}
+
+QDeclarativeListProperty<QObject> QQuickFlickable::flickableData()
+{
+ Q_D(QQuickFlickable);
+ return QDeclarativeListProperty<QObject>(this, (void *)d, QQuickFlickablePrivate::data_append,
+ QQuickFlickablePrivate::data_count,
+ QQuickFlickablePrivate::data_at,
+ QQuickFlickablePrivate::data_clear);
+}
+
+QDeclarativeListProperty<QQuickItem> QQuickFlickable::flickableChildren()
+{
+ Q_D(QQuickFlickable);
+ return QQuickItemPrivate::get(d->contentItem)->children();
+}
+
+/*!
+ \qmlproperty enumeration QtQuick2::Flickable::boundsBehavior
+ This property holds whether the surface may be dragged
+ beyond the Fickable's boundaries, or overshoot the
+ Flickable's boundaries when flicked.
+
+ This enables the feeling that the edges of the view are soft,
+ rather than a hard physical boundary.
+
+ The \c boundsBehavior can be one of:
+
+ \list
+ \o Flickable.StopAtBounds - the contents can not be dragged beyond the boundary
+ of the flickable, and flicks will not overshoot.
+ \o Flickable.DragOverBounds - the contents can be dragged beyond the boundary
+ of the Flickable, but flicks will not overshoot.
+ \o Flickable.DragAndOvershootBounds (default) - the contents can be dragged
+ beyond the boundary of the Flickable, and can overshoot the
+ boundary when flicked.
+ \endlist
+*/
+QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const
+{
+ Q_D(const QQuickFlickable);
+ return d->boundsBehavior;
+}
+
+void QQuickFlickable::setBoundsBehavior(BoundsBehavior b)
+{
+ Q_D(QQuickFlickable);
+ if (b == d->boundsBehavior)
+ return;
+ d->boundsBehavior = b;
+ emit boundsBehaviorChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::contentWidth
+ \qmlproperty real QtQuick2::Flickable::contentHeight
+
+ The dimensions of the content (the surface controlled by Flickable).
+ This should typically be set to the combined size of the items placed in the
+ Flickable.
+
+ The following snippet shows how these properties are used to display
+ an image that is larger than the Flickable item itself:
+
+ \snippet doc/src/snippets/declarative/flickable.qml document
+
+ In some cases, the the content dimensions can be automatically set
+ using the \l {Item::childrenRect.width}{childrenRect.width}
+ and \l {Item::childrenRect.height}{childrenRect.height} properties.
+*/
+qreal QQuickFlickable::contentWidth() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.viewSize;
+}
+
+void QQuickFlickable::setContentWidth(qreal w)
+{
+ Q_D(QQuickFlickable);
+ if (d->hData.viewSize == w)
+ return;
+ d->hData.viewSize = w;
+ if (w < 0)
+ d->contentItem->setWidth(width());
+ else
+ d->contentItem->setWidth(w);
+ d->hData.markExtentsDirty();
+ // Make sure that we're entirely in view.
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupX();
+ } else if (!d->pressed && d->hData.fixingUp) {
+ d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
+ d->fixupX();
+ }
+ emit contentWidthChanged();
+ d->updateBeginningEnd();
+}
+
+qreal QQuickFlickable::contentHeight() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.viewSize;
+}
+
+void QQuickFlickable::setContentHeight(qreal h)
+{
+ Q_D(QQuickFlickable);
+ if (d->vData.viewSize == h)
+ return;
+ d->vData.viewSize = h;
+ if (h < 0)
+ d->contentItem->setHeight(height());
+ else
+ d->contentItem->setHeight(h);
+ d->vData.markExtentsDirty();
+ // Make sure that we're entirely in view.
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupY();
+ } else if (!d->pressed && d->vData.fixingUp) {
+ d->fixupMode = QQuickFlickablePrivate::ExtentChanged;
+ d->fixupY();
+ }
+ emit contentHeightChanged();
+ d->updateBeginningEnd();
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::topMargin
+ \qmlproperty real QtQuick2::Flickable::leftMargin
+ \qmlproperty real QtQuick2::Flickable::bottomMargin
+ \qmlproperty real QtQuick2::Flickable::rightMargin
+
+ These properties hold the margins around the content. This space is reserved
+ in addition to the contentWidth and contentHeight.
+*/
+
+
+qreal QQuickFlickable::topMargin() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.startMargin;
+}
+
+void QQuickFlickable::setTopMargin(qreal m)
+{
+ Q_D(QQuickFlickable);
+ if (d->vData.startMargin == m)
+ return;
+ d->vData.startMargin = m;
+ d->vData.markExtentsDirty();
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupY();
+ }
+ emit topMarginChanged();
+ d->updateBeginningEnd();
+}
+
+qreal QQuickFlickable::bottomMargin() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.endMargin;
+}
+
+void QQuickFlickable::setBottomMargin(qreal m)
+{
+ Q_D(QQuickFlickable);
+ if (d->vData.endMargin == m)
+ return;
+ d->vData.endMargin = m;
+ d->vData.markExtentsDirty();
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupY();
+ }
+ emit bottomMarginChanged();
+ d->updateBeginningEnd();
+}
+
+qreal QQuickFlickable::leftMargin() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.startMargin;
+}
+
+void QQuickFlickable::setLeftMargin(qreal m)
+{
+ Q_D(QQuickFlickable);
+ if (d->hData.startMargin == m)
+ return;
+ d->hData.startMargin = m;
+ d->hData.markExtentsDirty();
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupX();
+ }
+ emit leftMarginChanged();
+ d->updateBeginningEnd();
+}
+
+qreal QQuickFlickable::rightMargin() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.endMargin;
+}
+
+void QQuickFlickable::setRightMargin(qreal m)
+{
+ Q_D(QQuickFlickable);
+ if (d->hData.endMargin == m)
+ return;
+ d->hData.endMargin = m;
+ d->hData.markExtentsDirty();
+ if (!d->pressed && !d->hData.moving && !d->vData.moving) {
+ d->fixupMode = QQuickFlickablePrivate::Immediate;
+ d->fixupX();
+ }
+ emit rightMarginChanged();
+ d->updateBeginningEnd();
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::xOrigin
+ \qmlproperty real QtQuick2::Flickable::yOrigin
+
+ These properties hold the origin of the content. This is usually (0,0), however
+ ListView and GridView may have an arbitrary origin due to delegate size variation,
+ or item insertion/removal outside the visible region.
+*/
+
+qreal QQuickFlickable::yOrigin() const
+{
+ Q_D(const QQuickFlickable);
+ return -minYExtent() + d->vData.startMargin;
+}
+
+qreal QQuickFlickable::xOrigin() const
+{
+ Q_D(const QQuickFlickable);
+ return -minXExtent() + d->hData.startMargin;
+}
+
+
+/*!
+ \qmlmethod QtQuick2::Flickable::resizeContent(real width, real height, QPointF center)
+
+ Resizes the content to \a width x \a height about \a center.
+
+ This does not scale the contents of the Flickable - it only resizes the \l contentWidth
+ and \l contentHeight.
+
+ Resizing the content may result in the content being positioned outside
+ the bounds of the Flickable. Calling \l returnToBounds() will
+ move the content back within legal bounds.
+*/
+void QQuickFlickable::resizeContent(qreal w, qreal h, QPointF center)
+{
+ Q_D(QQuickFlickable);
+ if (w != d->hData.viewSize) {
+ qreal oldSize = d->hData.viewSize;
+ d->hData.viewSize = w;
+ d->contentItem->setWidth(w);
+ emit contentWidthChanged();
+ if (center.x() != 0) {
+ qreal pos = center.x() * w / oldSize;
+ setContentX(contentX() + pos - center.x());
+ }
+ }
+ if (h != d->vData.viewSize) {
+ qreal oldSize = d->vData.viewSize;
+ d->vData.viewSize = h;
+ d->contentItem->setHeight(h);
+ emit contentHeightChanged();
+ if (center.y() != 0) {
+ qreal pos = center.y() * h / oldSize;
+ setContentY(contentY() + pos - center.y());
+ }
+ }
+ d->updateBeginningEnd();
+}
+
+/*!
+ \qmlmethod QtQuick2::Flickable::returnToBounds()
+
+ Ensures the content is within legal bounds.
+
+ This may be called to ensure that the content is within legal bounds
+ after manually positioning the content.
+*/
+void QQuickFlickable::returnToBounds()
+{
+ Q_D(QQuickFlickable);
+ d->fixupX();
+ d->fixupY();
+}
+
+qreal QQuickFlickable::vWidth() const
+{
+ Q_D(const QQuickFlickable);
+ if (d->hData.viewSize < 0)
+ return width();
+ else
+ return d->hData.viewSize;
+}
+
+qreal QQuickFlickable::vHeight() const
+{
+ Q_D(const QQuickFlickable);
+ if (d->vData.viewSize < 0)
+ return height();
+ else
+ return d->vData.viewSize;
+}
+
+bool QQuickFlickable::xflick() const
+{
+ Q_D(const QQuickFlickable);
+ if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
+ return vWidth() != width();
+ return d->flickableDirection & QQuickFlickable::HorizontalFlick;
+}
+
+bool QQuickFlickable::yflick() const
+{
+ Q_D(const QQuickFlickable);
+ if (d->flickableDirection == QQuickFlickable::AutoFlickDirection)
+ return vHeight() != height();
+ return d->flickableDirection & QQuickFlickable::VerticalFlick;
+}
+
+void QQuickFlickable::mouseUngrabEvent()
+{
+ Q_D(QQuickFlickable);
+ if (d->pressed) {
+ // if our mouse grab has been removed (probably by another Flickable),
+ // fix our state
+ d->pressed = false;
+ d->draggingEnding();
+ d->stealMouse = false;
+ setKeepMouseGrab(false);
+ }
+}
+
+bool QQuickFlickable::sendMouseEvent(QMouseEvent *event)
+{
+ Q_D(QQuickFlickable);
+ QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+
+ QQuickCanvas *c = canvas();
+ QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
+ bool disabledItem = grabber && !grabber->isEnabled();
+ bool stealThisEvent = d->stealMouse;
+ if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
+ QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+ event->button(), event->buttons(), event->modifiers());
+
+ mouseEvent.setAccepted(false);
+
+ switch (mouseEvent.type()) {
+ case QEvent::MouseMove:
+ d->handleMouseMoveEvent(&mouseEvent);
+ break;
+ case QEvent::MouseButtonPress:
+ if (d->pressed) // we are already pressed - this is a delayed replay
+ return false;
+
+ d->handleMousePressEvent(&mouseEvent);
+ d->captureDelayedPress(event);
+ stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
+ break;
+ case QEvent::MouseButtonRelease:
+ if (d->delayedPressEvent) {
+ // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay)
+ // so we reset the grabber
+ if (c->mouseGrabberItem() == d->delayedPressTarget)
+ d->delayedPressTarget->ungrabMouse();
+ //Use the event handler that will take care of finding the proper item to propagate the event
+ QQuickCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent);
+ d->clearDelayedPress();
+ // We send the release
+ canvas()->sendEvent(c->mouseGrabberItem(), event);
+ // And the event has been consumed
+ d->stealMouse = false;
+ d->pressed = false;
+ return true;
+ }
+ d->handleMouseReleaseEvent(&mouseEvent);
+ break;
+ default:
+ break;
+ }
+ grabber = qobject_cast<QQuickItem*>(c->mouseGrabberItem());
+ if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || disabledItem) {
+ d->clearDelayedPress();
+ grabMouse();
+ }
+
+ return stealThisEvent || d->delayedPressEvent || disabledItem;
+ } else if (d->lastPosTime.isValid()) {
+ d->lastPosTime.invalidate();
+ returnToBounds();
+ }
+ if (event->type() == QEvent::MouseButtonRelease) {
+ d->lastPosTime.invalidate();
+ d->clearDelayedPress();
+ d->stealMouse = false;
+ d->pressed = false;
+ }
+ return false;
+}
+
+
+bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e)
+{
+ Q_D(QQuickFlickable);
+ if (!isVisible() || !d->interactive || !isEnabled())
+ return QQuickItem::childMouseEventFilter(i, e);
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseMove:
+ case QEvent::MouseButtonRelease:
+ return sendMouseEvent(static_cast<QMouseEvent *>(e));
+ default:
+ break;
+ }
+
+ return QQuickItem::childMouseEventFilter(i, e);
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::maximumFlickVelocity
+ This property holds the maximum velocity that the user can flick the view in pixels/second.
+
+ The default value is platform dependent.
+*/
+qreal QQuickFlickable::maximumFlickVelocity() const
+{
+ Q_D(const QQuickFlickable);
+ return d->maxVelocity;
+}
+
+void QQuickFlickable::setMaximumFlickVelocity(qreal v)
+{
+ Q_D(QQuickFlickable);
+ if (v == d->maxVelocity)
+ return;
+ d->maxVelocity = v;
+ emit maximumFlickVelocityChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick2::Flickable::flickDeceleration
+ This property holds the rate at which a flick will decelerate.
+
+ The default value is platform dependent.
+*/
+qreal QQuickFlickable::flickDeceleration() const
+{
+ Q_D(const QQuickFlickable);
+ return d->deceleration;
+}
+
+void QQuickFlickable::setFlickDeceleration(qreal deceleration)
+{
+ Q_D(QQuickFlickable);
+ if (deceleration == d->deceleration)
+ return;
+ d->deceleration = deceleration;
+ emit flickDecelerationChanged();
+}
+
+bool QQuickFlickable::isFlicking() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.flicking || d->vData.flicking;
+}
+
+/*!
+ \qmlproperty bool QtQuick2::Flickable::flicking
+ \qmlproperty bool QtQuick2::Flickable::flickingHorizontally
+ \qmlproperty bool QtQuick2::Flickable::flickingVertically
+
+ These properties describe whether the view is currently moving horizontally,
+ vertically or in either direction, due to the user flicking the view.
+*/
+bool QQuickFlickable::isFlickingHorizontally() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.flicking;
+}
+
+bool QQuickFlickable::isFlickingVertically() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.flicking;
+}
+
+/*!
+ \qmlproperty bool QtQuick2::Flickable::dragging
+ \qmlproperty bool QtQuick2::Flickable::draggingHorizontally
+ \qmlproperty bool QtQuick2::Flickable::draggingVertically
+
+ These properties describe whether the view is currently moving horizontally,
+ vertically or in either direction, due to the user dragging the view.
+*/
+bool QQuickFlickable::isDragging() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.dragging || d->vData.dragging;
+}
+
+bool QQuickFlickable::isDraggingHorizontally() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.dragging;
+}
+
+bool QQuickFlickable::isDraggingVertically() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.dragging;
+}
+
+void QQuickFlickablePrivate::draggingStarting()
+{
+ Q_Q(QQuickFlickable);
+ bool wasDragging = hData.dragging || vData.dragging;
+ if (hMoved && !hData.dragging) {
+ hData.dragging = true;
+ emit q->draggingHorizontallyChanged();
+ }
+ if (vMoved && !vData.dragging) {
+ vData.dragging = true;
+ emit q->draggingVerticallyChanged();
+ }
+ if (!wasDragging && (hData.dragging || vData.dragging)) {
+ emit q->draggingChanged();
+ emit q->dragStarted();
+ }
+}
+
+void QQuickFlickablePrivate::draggingEnding()
+{
+ Q_Q(QQuickFlickable);
+ bool wasDragging = hData.dragging || vData.dragging;
+ if (hData.dragging) {
+ hData.dragging = false;
+ emit q->draggingHorizontallyChanged();
+ }
+ if (vData.dragging) {
+ vData.dragging = false;
+ emit q->draggingVerticallyChanged();
+ }
+ if (wasDragging && !hData.dragging && !vData.dragging) {
+ emit q->draggingChanged();
+ emit q->dragEnded();
+ }
+}
+
+/*!
+ \qmlproperty int QtQuick2::Flickable::pressDelay
+
+ This property holds the time to delay (ms) delivering a press to
+ children of the Flickable. This can be useful where reacting
+ to a press before a flicking action has undesirable effects.
+
+ If the flickable is dragged/flicked before the delay times out
+ the press event will not be delivered. If the button is released
+ within the timeout, both the press and release will be delivered.
+
+ Note that for nested Flickables with pressDelay set, the pressDelay of
+ inner Flickables is overridden by the outermost Flickable.
+*/
+int QQuickFlickable::pressDelay() const
+{
+ Q_D(const QQuickFlickable);
+ return d->pressDelay;
+}
+
+void QQuickFlickable::setPressDelay(int delay)
+{
+ Q_D(QQuickFlickable);
+ if (d->pressDelay == delay)
+ return;
+ d->pressDelay = delay;
+ emit pressDelayChanged();
+}
+
+/*!
+ \qmlproperty bool QtQuick2::Flickable::moving
+ \qmlproperty bool QtQuick2::Flickable::movingHorizontally
+ \qmlproperty bool QtQuick2::Flickable::movingVertically
+
+ These properties describe whether the view is currently moving horizontally,
+ vertically or in either direction, due to the user either dragging or
+ flicking the view.
+*/
+
+bool QQuickFlickable::isMoving() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.moving || d->vData.moving;
+}
+
+bool QQuickFlickable::isMovingHorizontally() const
+{
+ Q_D(const QQuickFlickable);
+ return d->hData.moving;
+}
+
+bool QQuickFlickable::isMovingVertically() const
+{
+ Q_D(const QQuickFlickable);
+ return d->vData.moving;
+}
+
+void QQuickFlickable::movementStarting()
+{
+ Q_D(QQuickFlickable);
+ if (d->hMoved && !d->hData.moving) {
+ d->hData.moving = true;
+ emit movingChanged();
+ emit movingHorizontallyChanged();
+ if (!d->vData.moving)
+ emit movementStarted();
+ }
+ else if (d->vMoved && !d->vData.moving) {
+ d->vData.moving = true;
+ emit movingChanged();
+ emit movingVerticallyChanged();
+ if (!d->hData.moving)
+ emit movementStarted();
+ }
+}
+
+void QQuickFlickable::movementEnding()
+{
+ Q_D(QQuickFlickable);
+ movementXEnding();
+ movementYEnding();
+ d->hData.smoothVelocity.setValue(0);
+ d->vData.smoothVelocity.setValue(0);
+}
+
+void QQuickFlickable::movementXEnding()
+{
+ Q_D(QQuickFlickable);
+ if (d->hData.flicking) {
+ d->hData.flicking = false;
+ emit flickingChanged();
+ emit flickingHorizontallyChanged();
+ if (!d->vData.flicking)
+ emit flickEnded();
+ }
+ if (!d->pressed && !d->stealMouse) {
+ if (d->hData.moving) {
+ d->hData.moving = false;
+ d->hMoved = false;
+ emit movingChanged();
+ emit movingHorizontallyChanged();
+ if (!d->vData.moving)
+ emit movementEnded();
+ }
+ }
+ d->hData.fixingUp = false;
+}
+
+void QQuickFlickable::movementYEnding()
+{
+ Q_D(QQuickFlickable);
+ if (d->vData.flicking) {
+ d->vData.flicking = false;
+ emit flickingChanged();
+ emit flickingVerticallyChanged();
+ if (!d->hData.flicking)
+ emit flickEnded();
+ }
+ if (!d->pressed && !d->stealMouse) {
+ if (d->vData.moving) {
+ d->vData.moving = false;
+ d->vMoved = false;
+ emit movingChanged();
+ emit movingVerticallyChanged();
+ if (!d->hData.moving)
+ emit movementEnded();
+ }
+ }
+ d->vData.fixingUp = false;
+}
+
+void QQuickFlickablePrivate::updateVelocity()
+{
+ Q_Q(QQuickFlickable);
+ emit q->horizontalVelocityChanged();
+ emit q->verticalVelocityChanged();
+}
+
+QT_END_NAMESPACE