| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickflickable_p.h" |
| #include "qquickflickable_p_p.h" |
| #include "qquickflickablebehavior_p.h" |
| #include "qquickwindow.h" |
| #include "qquickwindow_p.h" |
| #include "qquickevents_p_p.h" |
| |
| #include <QtQuick/private/qquickpointerhandler_p.h> |
| #include <QtQuick/private/qquicktransition_p.h> |
| #include <private/qqmlglobal_p.h> |
| |
| #include <QtQml/qqmlinfo.h> |
| #include <QtGui/qevent.h> |
| #include <QtGui/qguiapplication.h> |
| #include <QtGui/private/qguiapplication_p.h> |
| #include <QtGui/qstylehints.h> |
| #include <QtCore/qmath.h> |
| #include "qplatformdefs.h" |
| |
| #include <math.h> |
| #include <cmath> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent) |
| |
| // FlickThreshold determines how far the "mouse" must have moved |
| // before we perform a flick. |
| static const int FlickThreshold = 15; |
| |
| // RetainGrabVelocity is the maxmimum instantaneous velocity that |
| // will ensure the Flickable retains the grab on consecutive flicks. |
| static const int RetainGrabVelocity = 100; |
| |
| // Currently std::round can't be used on Android when using ndk g++, so |
| // use C version instead. We could just define two versions of Round, one |
| // for float and one for double, but then only one of them would be used |
| // and compiler would trigger a warning about unused function. |
| // |
| // See https://code.google.com/p/android/issues/detail?id=54418 |
| template<typename T> |
| static T Round(T t) { |
| return round(t); |
| } |
| template<> |
| Q_DECL_UNUSED float Round<float>(float f) { |
| return roundf(f); |
| } |
| |
| static qreal EaseOvershoot(qreal t) { |
| return qAtan(t); |
| } |
| |
| 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); |
| } |
| |
| |
| class QQuickFlickableReboundTransition : public QQuickTransitionManager |
| { |
| public: |
| QQuickFlickableReboundTransition(QQuickFlickable *f, const QString &name) |
| : flickable(f), axisData(nullptr), propName(name), active(false) |
| { |
| } |
| |
| ~QQuickFlickableReboundTransition() |
| { |
| flickable = nullptr; |
| } |
| |
| bool startTransition(QQuickFlickablePrivate::AxisData *data, qreal toPos) { |
| QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(flickable); |
| if (!fp->rebound || !fp->rebound->enabled()) |
| return false; |
| active = true; |
| axisData = data; |
| axisData->transitionTo = toPos; |
| axisData->transitionToSet = true; |
| |
| actions.clear(); |
| actions << QQuickStateAction(fp->contentItem, propName, toPos); |
| QQuickTransitionManager::transition(actions, fp->rebound, fp->contentItem); |
| return true; |
| } |
| |
| bool isActive() const { |
| return active; |
| } |
| |
| void stopTransition() { |
| if (!flickable || !isRunning()) |
| return; |
| QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(flickable); |
| if (axisData == &fp->hData) |
| axisData->move.setValue(-flickable->contentX()); |
| else |
| axisData->move.setValue(-flickable->contentY()); |
| active = false; |
| cancel(); |
| } |
| |
| protected: |
| void finished() override { |
| if (!flickable) |
| return; |
| axisData->move.setValue(axisData->transitionTo); |
| QQuickFlickablePrivate *fp = QQuickFlickablePrivate::get(flickable); |
| active = false; |
| |
| if (!fp->hData.transitionToBounds->isActive() |
| && !fp->vData.transitionToBounds->isActive()) { |
| flickable->movementEnding(); |
| } |
| } |
| |
| private: |
| QQuickStateOperation::ActionList actions; |
| QQuickFlickable *flickable; |
| QQuickFlickablePrivate::AxisData *axisData; |
| QString propName; |
| bool active; |
| }; |
| |
| QQuickFlickablePrivate::AxisData::~AxisData() |
| { |
| delete transitionToBounds; |
| } |
| |
| |
| QQuickFlickablePrivate::QQuickFlickablePrivate() |
| : contentItem(new QQuickItem) |
| , hData(this, &QQuickFlickablePrivate::setViewportX) |
| , vData(this, &QQuickFlickablePrivate::setViewportY) |
| , hMoved(false), vMoved(false) |
| , stealMouse(false), pressed(false) |
| , scrollingPhase(false), interactive(true), calcVelocity(false) |
| , pixelAligned(false) |
| , syncDrag(false) |
| , lastPosTime(-1) |
| , lastPressTime(0) |
| , deceleration(QML_FLICK_DEFAULTDECELERATION) |
| , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100) |
| , delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400) |
| , flickBoost(1.0), fixupMode(Normal), vTime(0), visibleArea(nullptr) |
| , flickableDirection(QQuickFlickable::AutoFlickDirection) |
| , boundsBehavior(QQuickFlickable::DragAndOvershootBounds) |
| , boundsMovement(QQuickFlickable::FollowBoundsBehavior) |
| , rebound(nullptr) |
| { |
| } |
| |
| void QQuickFlickablePrivate::init() |
| { |
| Q_Q(QQuickFlickable); |
| QQml_setParent_noEvent(contentItem, q); |
| contentItem->setParentItem(q); |
| qmlobject_connect(&timeline, QQuickTimeLine, SIGNAL(completed()), |
| q, QQuickFlickable, SLOT(timelineCompleted())); |
| qmlobject_connect(&velocityTimeline, QQuickTimeLine, SIGNAL(completed()), |
| q, QQuickFlickable, SLOT(velocityTimelineCompleted())); |
| q->setAcceptedMouseButtons(Qt::LeftButton); |
| q->setAcceptTouchEvents(false); // rely on mouse events synthesized from touch |
| q->setFiltersChildMouseEvents(true); |
| QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem); |
| viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry); |
| } |
| |
| /* |
| Returns the amount to overshoot by given a velocity. |
| Will be roughly in range 0 - size/4 |
| */ |
| qreal QQuickFlickablePrivate::overShootDistance(qreal size) const |
| { |
| 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, QQuickGeometryChange change, const QRectF &) |
| { |
| Q_Q(QQuickFlickable); |
| if (item == contentItem) { |
| Qt::Orientations orient = nullptr; |
| if (change.xChange()) |
| orient |= Qt::Horizontal; |
| if (change.yChange()) |
| orient |= Qt::Vertical; |
| if (orient) |
| q->viewportMoved(orient); |
| if (orient & Qt::Horizontal) |
| emit q->contentXChanged(); |
| if (orient & Qt::Vertical) |
| emit q->contentYChanged(); |
| } |
| } |
| |
| bool QQuickFlickablePrivate::flickX(qreal velocity) |
| { |
| Q_Q(QQuickFlickable); |
| return flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity); |
| } |
| |
| bool QQuickFlickablePrivate::flickY(qreal velocity) |
| { |
| Q_Q(QQuickFlickable); |
| return flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity); |
| } |
| |
| bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal, |
| QQuickTimeLineCallback::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 || boundsBehavior & QQuickFlickable::OvershootBounds) { |
| qreal v = velocity; |
| if (maxVelocity != -1 && maxVelocity < qAbs(v)) { |
| if (v < 0) |
| v = -maxVelocity; |
| else |
| v = maxVelocity; |
| } |
| |
| // adjust accel so that we hit a full pixel |
| qreal accel = deceleration; |
| qreal v2 = v * v; |
| qreal dist = v2 / (accel * 2.0); |
| if (v > 0) |
| dist = -dist; |
| qreal target = -Round(-(data.move.value() - dist)); |
| dist = -target + data.move.value(); |
| accel = v2 / (2.0f * qAbs(dist)); |
| |
| resetTimeline(data); |
| if (!data.inOvershoot) { |
| if (boundsBehavior & QQuickFlickable::OvershootBounds) |
| timeline.accel(data.move, v, accel); |
| else |
| timeline.accel(data.move, v, accel, maxDistance); |
| } |
| timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| |
| if (&data == &hData) |
| return !hData.flicking && q->xflick(); |
| else if (&data == &vData) |
| return !vData.flicking && q->yflick(); |
| return false; |
| } else { |
| resetTimeline(data); |
| fixup(data, minExtent, maxExtent); |
| return false; |
| } |
| } |
| |
| void QQuickFlickablePrivate::fixupY_callback(void *data) |
| { |
| ((QQuickFlickablePrivate *)data)->fixupY(); |
| } |
| |
| void QQuickFlickablePrivate::fixupX_callback(void *data) |
| { |
| ((QQuickFlickablePrivate *)data)->fixupX(); |
| } |
| |
| void QQuickFlickablePrivate::fixupX() |
| { |
| Q_Q(QQuickFlickable); |
| if (!q->isComponentComplete()) |
| return; //Do not fixup from initialization values |
| fixup(hData, q->minXExtent(), q->maxXExtent()); |
| } |
| |
| void QQuickFlickablePrivate::fixupY() |
| { |
| Q_Q(QQuickFlickable); |
| if (!q->isComponentComplete()) |
| return; //Do not fixup from initialization values |
| fixup(vData, q->minYExtent(), q->maxYExtent()); |
| } |
| |
| void QQuickFlickablePrivate::adjustContentPos(AxisData &data, qreal toPos) |
| { |
| Q_Q(QQuickFlickable); |
| switch (fixupMode) { |
| case Immediate: |
| timeline.set(data.move, toPos); |
| 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, toPos, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); |
| data.fixingUp = true; |
| break; |
| default: { |
| if (data.transitionToBounds && data.transitionToBounds->startTransition(&data, toPos)) { |
| q->movementStarting(); |
| data.fixingUp = true; |
| } else { |
| qreal dist = toPos - data.move; |
| timeline.move(data.move, toPos - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4); |
| timeline.move(data.move, toPos, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); |
| data.fixingUp = true; |
| } |
| } |
| } |
| } |
| |
| void QQuickFlickablePrivate::resetTimeline(AxisData &data) |
| { |
| timeline.reset(data.move); |
| if (data.transitionToBounds) |
| data.transitionToBounds->stopTransition(); |
| } |
| |
| void QQuickFlickablePrivate::clearTimeline() |
| { |
| timeline.clear(); |
| if (hData.transitionToBounds) |
| hData.transitionToBounds->stopTransition(); |
| if (vData.transitionToBounds) |
| vData.transitionToBounds->stopTransition(); |
| } |
| |
| void QQuickFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) |
| { |
| if (data.move.value() >= minExtent || maxExtent > minExtent) { |
| resetTimeline(data); |
| if (data.move.value() != minExtent) { |
| adjustContentPos(data, minExtent); |
| } |
| } else if (data.move.value() <= maxExtent) { |
| resetTimeline(data); |
| adjustContentPos(data, maxExtent); |
| } else if (-Round(-data.move.value()) != data.move.value()) { |
| // We could animate, but since it is less than 0.5 pixel it's probably not worthwhile. |
| resetTimeline(data); |
| qreal val = data.move.value(); |
| if (std::abs(-Round(-val) - val) < 0.25) // round small differences |
| val = -Round(-val); |
| else if (data.smoothVelocity.value() > 0) // continue direction of motion for larger |
| val = -std::floor(-val); |
| else if (data.smoothVelocity.value() < 0) |
| val = -std::ceil(-val); |
| else // otherwise round |
| val = -Round(-val); |
| timeline.set(data.move, val); |
| } |
| data.inOvershoot = false; |
| fixupMode = Normal; |
| data.vTime = timeline.time(); |
| } |
| |
| static bool fuzzyLessThanOrEqualTo(qreal a, qreal b) |
| { |
| if (a == 0.0 || b == 0.0) { |
| // qFuzzyCompare is broken |
| a += 1.0; |
| b += 1.0; |
| } |
| return a <= b || qFuzzyCompare(a, b); |
| } |
| |
| void QQuickFlickablePrivate::updateBeginningEnd() |
| { |
| Q_Q(QQuickFlickable); |
| bool atXBeginningChange = false, atXEndChange = false; |
| bool atYBeginningChange = false, atYEndChange = false; |
| |
| // Vertical |
| const qreal maxyextent = -q->maxYExtent(); |
| const qreal minyextent = -q->minYExtent(); |
| const qreal ypos = -vData.move.value(); |
| bool atBeginning = fuzzyLessThanOrEqualTo(ypos, minyextent); |
| bool atEnd = fuzzyLessThanOrEqualTo(maxyextent, ypos); |
| |
| if (atBeginning != vData.atBeginning) { |
| vData.atBeginning = atBeginning; |
| atYBeginningChange = true; |
| } |
| if (atEnd != vData.atEnd) { |
| vData.atEnd = atEnd; |
| atYEndChange = true; |
| } |
| |
| // Horizontal |
| const qreal maxxextent = -q->maxXExtent(); |
| const qreal minxextent = -q->minXExtent(); |
| const qreal xpos = -hData.move.value(); |
| atBeginning = fuzzyLessThanOrEqualTo(xpos, minxextent); |
| atEnd = fuzzyLessThanOrEqualTo(maxxextent, xpos); |
| |
| if (atBeginning != hData.atBeginning) { |
| hData.atBeginning = atBeginning; |
| atXBeginningChange = true; |
| } |
| if (atEnd != hData.atEnd) { |
| hData.atEnd = atEnd; |
| atXEndChange = true; |
| } |
| |
| if (vData.extentsChanged) { |
| vData.extentsChanged = false; |
| qreal originY = q->originY(); |
| if (vData.origin != originY) { |
| vData.origin = originY; |
| emit q->originYChanged(); |
| } |
| } |
| |
| if (hData.extentsChanged) { |
| hData.extentsChanged = false; |
| qreal originX = q->originX(); |
| if (hData.origin != originX) { |
| hData.origin = originX; |
| emit q->originXChanged(); |
| } |
| } |
| |
| if (atXEndChange || atYEndChange || atXBeginningChange || atYBeginningChange) |
| emit q->isAtBoundaryChanged(); |
| if (atXEndChange) |
| emit q->atXEndChanged(); |
| if (atXBeginningChange) |
| emit q->atXBeginningChanged(); |
| if (atYEndChange) |
| emit q->atYEndChanged(); |
| if (atYBeginningChange) |
| emit q->atYBeginningChanged(); |
| |
| if (visibleArea) |
| visibleArea->updateVisible(); |
| } |
| |
| /*! |
| \qmlsignal QtQuick::Flickable::dragStarted() |
| |
| This signal is emitted when the view starts to be dragged due to user |
| interaction. |
| |
| The corresponding handler is \c onDragStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::Flickable::dragEnded() |
| |
| This signal is emitted when the user stops dragging the view. |
| |
| If the velocity of the drag is sufficient at the time the |
| touch/mouse button is released then a flick will start. |
| |
| The corresponding handler is \c onDragEnded. |
| */ |
| |
| /*! |
| \qmltype Flickable |
| \instantiates QQuickFlickable |
| \inqmlmodule QtQuick |
| \ingroup qtquick-input |
| \ingroup qtquick-containers |
| |
| \brief 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 qml/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 Examples of contentX and contentY |
| |
| The following images demonstrate a flickable being flicked in various |
| directions and the resulting \l contentX and \l contentY values. |
| The blue square represents the flickable's content, and the black |
| border represents the bounds of the flickable. |
| |
| \table |
| \row |
| \li \image flickable-contentXY-resting.png |
| \li The \c contentX and \c contentY are both \c 0. |
| \row |
| \li \image flickable-contentXY-top-left.png |
| \li The \c contentX and the \c contentY are both \c 50. |
| \row |
| \li \image flickable-contentXY-top-right.png |
| \li The \c contentX is \c -50 and the \c contentY is \c 50. |
| \row |
| \li \image flickable-contentXY-bottom-right.png |
| \li The \c contentX and the \c contentY are both \c -50. |
| \row |
| \li \image flickable-contentXY-bottom-left.png |
| \li The \c contentX is \c 50 and the \c contentY is \c -50. |
| \endtable |
| |
| \section1 Limitations |
| |
| \note Due to an implementation detail, items placed inside a Flickable |
| cannot anchor to the Flickable. Instead, use \l {Item::}{parent}, which |
| refers to the Flickable's \l contentItem. The size of the content item is |
| determined by \l contentWidth and \l contentHeight. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::Flickable::movementStarted() |
| |
| This signal is emitted when the view begins moving due to user |
| interaction or a generated flick(). |
| |
| The corresponding handler is \c onMovementStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::Flickable::movementEnded() |
| |
| This signal is emitted when the view stops moving due to user |
| interaction or a generated flick(). If a flick was active, this signal will |
| be emitted once the flick stops. If a flick was not |
| active, this signal will be emitted when the |
| user stops dragging - i.e. a mouse or touch release. |
| |
| The corresponding handler is \c onMovementEnded. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::Flickable::flickStarted() |
| |
| This signal is emitted when the view is flicked. A flick |
| starts from the point that the mouse or touch is released, |
| while still in motion. |
| |
| The corresponding handler is \c onFlickStarted. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::Flickable::flickEnded() |
| |
| This signal is emitted when the view stops moving due to a flick. |
| |
| The corresponding handler is \c onFlickEnded. |
| */ |
| |
| /*! |
| \qmlpropertygroup QtQuick::Flickable::visibleArea |
| \qmlproperty real QtQuick::Flickable::visibleArea.xPosition |
| \qmlproperty real QtQuick::Flickable::visibleArea.widthRatio |
| \qmlproperty real QtQuick::Flickable::visibleArea.yPosition |
| \qmlproperty real QtQuick::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 qml/flickableScrollbar.qml 0 |
| \dots 8 |
| \snippet qml/flickableScrollbar.qml 1 |
| |
| \sa {customitems/scrollbar}{UI Components: 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 QtQuick::Flickable::contentX |
| \qmlproperty real QtQuick::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 increase by 100. |
| |
| \note If you flick back to the origin (the top-left corner), after the |
| rebound animation, \c contentX will settle to the same value as \c originX, |
| and \c contentY to \c originY. These are 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. So if you want to |
| implement something like a vertical scrollbar, one way is to use |
| \c {y: (contentY - originY) * (height / contentHeight)} |
| for the position; another way is to use the normalized values in |
| \l {QtQuick::Flickable::visibleArea}{visibleArea}. |
| |
| \sa {Examples of contentX and contentY}, originX, originY |
| */ |
| 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->resetTimeline(d->hData); |
| d->hData.vTime = d->timeline.time(); |
| if (isMoving() || isFlicking()) |
| movementEnding(true, false); |
| if (!qFuzzyCompare(-pos, d->hData.move.value())) |
| d->hData.move.setValue(-pos); |
| } |
| |
| 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->resetTimeline(d->vData); |
| d->vData.vTime = d->timeline.time(); |
| if (isMoving() || isFlicking()) |
| movementEnding(false, true); |
| if (!qFuzzyCompare(-pos, d->vData.move.value())) |
| d->vData.move.setValue(-pos); |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::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->cancelInteraction(); |
| } |
| emit interactiveChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Flickable::horizontalVelocity |
| \qmlproperty real QtQuick::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. |
| |
| Note that for views with a large content size (more than 10 times the view size), |
| the velocity of the flick may exceed the velocity of the touch in the case |
| of multiple quick consecutive flicks. This allows the user to flick faster |
| through large content. |
| */ |
| 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 QtQuick::Flickable::atXBeginning |
| \qmlproperty bool QtQuick::Flickable::atXEnd |
| \qmlproperty bool QtQuick::Flickable::atYBeginning |
| \qmlproperty bool QtQuick::Flickable::atYEnd |
| |
| These properties are true if the flickable view is positioned at the beginning, |
| or end respectively. |
| */ |
| 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; |
| } |
| |
| /*! |
| \qmlproperty Item QtQuick::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() const |
| { |
| Q_D(const QQuickFlickable); |
| return d->contentItem; |
| } |
| |
| QQuickFlickableVisibleArea *QQuickFlickable::visibleArea() |
| { |
| Q_D(QQuickFlickable); |
| if (!d->visibleArea) { |
| d->visibleArea = new QQuickFlickableVisibleArea(this); |
| d->visibleArea->updateVisible(); // calculate initial ratios |
| } |
| return d->visibleArea; |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Flickable::flickableDirection |
| |
| This property determines which directions the view can be flicked. |
| |
| \list |
| \li 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. |
| \li Flickable.AutoFlickIfNeeded - allows flicking vertically if the |
| \e contentHeight is greater than the \e height of the Flickable. |
| Allows flicking horizontally if the \e contentWidth is greater than |
| to the \e width of the Flickable. (since \c{QtQuick 2.7}) |
| \li Flickable.HorizontalFlick - allows flicking horizontally. |
| \li Flickable.VerticalFlick - allows flicking vertically. |
| \li 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(); |
| } |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::Flickable::pixelAligned |
| |
| This property sets the alignment of \l contentX and \l contentY to |
| pixels (\c true) or subpixels (\c false). |
| |
| Enable pixelAligned to optimize for still content or moving content with |
| high constrast edges, such as one-pixel-wide lines, text or vector graphics. |
| Disable pixelAligned when optimizing for animation quality. |
| |
| The default is \c false. |
| */ |
| 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(); |
| } |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::Flickable::synchronousDrag |
| \since 5.12 |
| |
| If this property is set to true, then when the mouse or touchpoint moves |
| far enough to begin dragging the content, the content will jump, such that |
| the content pixel which was under the cursor or touchpoint when pressed |
| remains under that point. |
| |
| The default is \c false, which provides a smoother experience (no jump) |
| at the cost that some of the drag distance is "lost" at the beginning. |
| */ |
| bool QQuickFlickable::synchronousDrag() const |
| { |
| Q_D(const QQuickFlickable); |
| return d->syncDrag; |
| } |
| |
| void QQuickFlickable::setSynchronousDrag(bool v) |
| { |
| Q_D(QQuickFlickable); |
| if (v != d->syncDrag) { |
| d->syncDrag = v; |
| emit synchronousDragChanged(); |
| } |
| } |
| |
| qint64 QQuickFlickablePrivate::computeCurrentTime(QInputEvent *event) const |
| { |
| if (0 != event->timestamp()) |
| return event->timestamp(); |
| if (!timer.isValid()) |
| return 0LL; |
| return timer.elapsed(); |
| } |
| |
| qreal QQuickFlickablePrivate::devicePixelRatio() const |
| { |
| return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio()); |
| } |
| |
| void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) |
| { |
| Q_Q(QQuickFlickable); |
| timer.start(); |
| if (interactive && timeline.isActive() |
| && ((qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity && !hData.fixingUp && !hData.inOvershoot) |
| || (qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity && !vData.fixingUp && !vData.inOvershoot))) { |
| stealMouse = true; // If we've been flicked then steal the click. |
| int flickTime = timeline.time(); |
| if (flickTime > 600) { |
| // too long between flicks - cancel boost |
| hData.continuousFlickVelocity = 0; |
| vData.continuousFlickVelocity = 0; |
| flickBoost = 1.0; |
| } else { |
| hData.continuousFlickVelocity = -hData.smoothVelocity.value(); |
| vData.continuousFlickVelocity = -vData.smoothVelocity.value(); |
| if (flickTime > 300) // slower flicking - reduce boost |
| flickBoost = qMax(1.0, flickBoost - 0.5); |
| } |
| } else { |
| stealMouse = false; |
| hData.continuousFlickVelocity = 0; |
| vData.continuousFlickVelocity = 0; |
| flickBoost = 1.0; |
| } |
| q->setKeepMouseGrab(stealMouse); |
| |
| maybeBeginDrag(computeCurrentTime(event), event->localPos()); |
| } |
| |
| void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn) |
| { |
| Q_Q(QQuickFlickable); |
| clearDelayedPress(); |
| pressed = true; |
| |
| if (hData.transitionToBounds) |
| hData.transitionToBounds->stopTransition(); |
| if (vData.transitionToBounds) |
| vData.transitionToBounds->stopTransition(); |
| if (!hData.fixingUp) |
| resetTimeline(hData); |
| if (!vData.fixingUp) |
| resetTimeline(vData); |
| |
| hData.reset(); |
| vData.reset(); |
| hData.dragMinBound = q->minXExtent() - hData.startMargin; |
| vData.dragMinBound = q->minYExtent() - vData.startMargin; |
| hData.dragMaxBound = q->maxXExtent() + hData.endMargin; |
| vData.dragMaxBound = q->maxYExtent() + vData.endMargin; |
| fixupMode = Normal; |
| lastPos = QPointF(); |
| pressPos = pressPosn; |
| hData.pressPos = hData.move.value(); |
| vData.pressPos = vData.move.value(); |
| bool wasFlicking = hData.flicking || vData.flicking; |
| if (hData.flicking) { |
| hData.flicking = false; |
| emit q->flickingHorizontallyChanged(); |
| } |
| if (vData.flicking) { |
| vData.flicking = false; |
| emit q->flickingVerticallyChanged(); |
| } |
| if (wasFlicking) |
| emit q->flickingChanged(); |
| lastPosTime = lastPressTime = currentTimestamp; |
| vData.velocityTime.start(); |
| hData.velocityTime.start(); |
| } |
| |
| void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, |
| const QVector2D &deltas, bool overThreshold, bool momentum, |
| bool velocitySensitiveOverBounds, const QVector2D &velocity) |
| { |
| Q_Q(QQuickFlickable); |
| bool rejectY = false; |
| bool rejectX = false; |
| |
| bool keepY = q->yflick(); |
| bool keepX = q->xflick(); |
| |
| bool stealY = false; |
| bool stealX = false; |
| if (eventType == QEvent::MouseMove) { |
| stealX = stealY = stealMouse; |
| } else if (eventType == QEvent::Wheel) { |
| stealX = stealY = scrollingPhase; |
| } |
| |
| bool prevHMoved = hMoved; |
| bool prevVMoved = vMoved; |
| |
| qint64 elapsedSincePress = currentTimestamp - lastPressTime; |
| |
| if (q->yflick()) { |
| qreal dy = deltas.y(); |
| if (overThreshold || elapsedSincePress > 200) { |
| if (!vMoved) |
| vData.dragStartOffset = dy; |
| qreal newY = dy + vData.pressPos - (syncDrag ? 0 : vData.dragStartOffset); |
| // Recalculate bounds in case margins have changed, but use the content |
| // size estimate taken at the start of the drag in case the drag causes |
| // the estimate to be altered |
| const qreal minY = vData.dragMinBound + vData.startMargin; |
| const qreal maxY = vData.dragMaxBound - vData.endMargin; |
| if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) { |
| if (fuzzyLessThanOrEqualTo(newY, maxY)) { |
| newY = maxY; |
| rejectY = vData.pressPos == maxY && vData.move.value() == maxY && dy < 0; |
| } |
| if (fuzzyLessThanOrEqualTo(minY, newY)) { |
| newY = minY; |
| rejectY |= vData.pressPos == minY && vData.move.value() == minY && dy > 0; |
| } |
| } else { |
| qreal vel = velocity.y() / QML_FLICK_OVERSHOOTFRICTION; |
| if (vel > 0. && vel > vData.velocity) |
| vData.velocity = qMin(velocity.y() / QML_FLICK_OVERSHOOTFRICTION, float(QML_FLICK_DEFAULTMAXVELOCITY)); |
| else if (vel < 0. && vel < vData.velocity) |
| vData.velocity = qMax(velocity.y() / QML_FLICK_OVERSHOOTFRICTION, -float(QML_FLICK_DEFAULTMAXVELOCITY)); |
| if (newY > minY) { |
| // Overshoot beyond the top. But don't wait for momentum phase to end before returning to bounds. |
| if (momentum && vData.atBeginning) { |
| if (!vData.inRebound) { |
| vData.inRebound = true; |
| q->returnToBounds(); |
| } |
| return; |
| } |
| if (velocitySensitiveOverBounds) { |
| qreal overshoot = (newY - minY) * vData.velocity / QML_FLICK_DEFAULTMAXVELOCITY / QML_FLICK_OVERSHOOTFRICTION; |
| overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| newY = minY + overshoot; |
| } else { |
| newY = minY + (newY - minY) / 2; |
| } |
| } else if (newY < maxY && maxY - minY <= 0) { |
| // Overshoot beyond the bottom. But don't wait for momentum phase to end before returning to bounds. |
| if (momentum && vData.atEnd) { |
| if (!vData.inRebound) { |
| vData.inRebound = true; |
| q->returnToBounds(); |
| } |
| return; |
| } |
| if (velocitySensitiveOverBounds) { |
| qreal overshoot = (newY - maxY) * vData.velocity / QML_FLICK_DEFAULTMAXVELOCITY / QML_FLICK_OVERSHOOTFRICTION; |
| overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| newY = maxY - overshoot; |
| } else { |
| newY = maxY + (newY - maxY) / 2; |
| } |
| } |
| } |
| if (!rejectY && stealMouse && dy != 0.0 && dy != vData.previousDragDelta) { |
| clearTimeline(); |
| vData.move.setValue(newY); |
| vMoved = true; |
| } |
| if (!rejectY && overThreshold) |
| stealY = true; |
| |
| if ((newY >= minY && vData.pressPos == minY && vData.move.value() == minY && dy > 0) |
| || (newY <= maxY && vData.pressPos == maxY && vData.move.value() == maxY && dy < 0)) { |
| keepY = false; |
| } |
| } |
| vData.previousDragDelta = dy; |
| } |
| |
| if (q->xflick()) { |
| qreal dx = deltas.x(); |
| if (overThreshold || elapsedSincePress > 200) { |
| if (!hMoved) |
| hData.dragStartOffset = dx; |
| qreal newX = dx + hData.pressPos - (syncDrag ? 0 : hData.dragStartOffset); |
| const qreal minX = hData.dragMinBound + hData.startMargin; |
| const qreal maxX = hData.dragMaxBound - hData.endMargin; |
| if (!(boundsBehavior & QQuickFlickable::DragOverBounds)) { |
| if (fuzzyLessThanOrEqualTo(newX, maxX)) { |
| newX = maxX; |
| rejectX = hData.pressPos == maxX && hData.move.value() == maxX && dx < 0; |
| } |
| if (fuzzyLessThanOrEqualTo(minX, newX)) { |
| newX = minX; |
| rejectX |= hData.pressPos == minX && hData.move.value() == minX && dx > 0; |
| } |
| } else { |
| qreal vel = velocity.x() / QML_FLICK_OVERSHOOTFRICTION; |
| if (vel > 0. && vel > hData.velocity) |
| hData.velocity = qMin(velocity.x() / QML_FLICK_OVERSHOOTFRICTION, float(QML_FLICK_DEFAULTMAXVELOCITY)); |
| else if (vel < 0. && vel < hData.velocity) |
| hData.velocity = qMax(velocity.x() / QML_FLICK_OVERSHOOTFRICTION, -float(QML_FLICK_DEFAULTMAXVELOCITY)); |
| if (newX > minX) { |
| // Overshoot beyond the left. But don't wait for momentum phase to end before returning to bounds. |
| if (momentum && hData.atBeginning) { |
| if (!hData.inRebound) { |
| hData.inRebound = true; |
| q->returnToBounds(); |
| } |
| return; |
| } |
| if (velocitySensitiveOverBounds) { |
| qreal overshoot = (newX - minX) * hData.velocity / QML_FLICK_DEFAULTMAXVELOCITY / QML_FLICK_OVERSHOOTFRICTION; |
| overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| newX = minX + overshoot; |
| } else { |
| newX = minX + (newX - minX) / 2; |
| } |
| } else if (newX < maxX && maxX - minX <= 0) { |
| // Overshoot beyond the right. But don't wait for momentum phase to end before returning to bounds. |
| if (momentum && hData.atEnd) { |
| if (!hData.inRebound) { |
| hData.inRebound = true; |
| q->returnToBounds(); |
| } |
| return; |
| } |
| if (velocitySensitiveOverBounds) { |
| qreal overshoot = (newX - maxX) * hData.velocity / QML_FLICK_DEFAULTMAXVELOCITY / QML_FLICK_OVERSHOOTFRICTION; |
| overshoot = QML_FLICK_OVERSHOOT * devicePixelRatio() * EaseOvershoot(overshoot / QML_FLICK_OVERSHOOT / devicePixelRatio()); |
| newX = maxX - overshoot; |
| } else { |
| newX = maxX + (newX - maxX) / 2; |
| } |
| } |
| } |
| |
| if (!rejectX && stealMouse && dx != 0.0 && dx != hData.previousDragDelta) { |
| clearTimeline(); |
| hData.move.setValue(newX); |
| hMoved = true; |
| } |
| |
| if (!rejectX && overThreshold) |
| stealX = true; |
| |
| if ((newX >= minX && vData.pressPos == minX && vData.move.value() == minX && dx > 0) |
| || (newX <= maxX && vData.pressPos == maxX && vData.move.value() == maxX && dx < 0)) { |
| keepX = false; |
| } |
| } |
| hData.previousDragDelta = dx; |
| } |
| |
| stealMouse = stealX || stealY; |
| if (stealMouse) { |
| if ((stealX && keepX) || (stealY && keepY)) |
| q->setKeepMouseGrab(true); |
| clearDelayedPress(); |
| } |
| |
| if (rejectY) { |
| vData.velocityBuffer.clear(); |
| vData.velocity = 0; |
| } |
| if (rejectX) { |
| hData.velocityBuffer.clear(); |
| hData.velocity = 0; |
| } |
| |
| if (momentum && !hData.flicking && !vData.flicking) |
| flickingStarted(hData.velocity != 0, vData.velocity != 0); |
| draggingStarting(); |
| |
| if ((hMoved && !prevHMoved) || (vMoved && !prevVMoved)) |
| q->movementStarting(); |
| |
| lastPosTime = currentTimestamp; |
| if (q->yflick() && !rejectY) |
| vData.addVelocitySample(velocity.y(), maxVelocity); |
| if (q->xflick() && !rejectX) |
| hData.addVelocitySample(velocity.x(), maxVelocity); |
| lastPos = localPos; |
| } |
| |
| void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) |
| { |
| Q_Q(QQuickFlickable); |
| if (!interactive || lastPosTime == -1 || event->buttons() == Qt::NoButton) |
| return; |
| |
| qint64 currentTimestamp = computeCurrentTime(event); |
| QVector2D deltas = QVector2D(event->localPos() - pressPos); |
| bool overThreshold = false; |
| QVector2D velocity = QGuiApplicationPrivate::mouseEventVelocity(event); |
| // TODO guarantee that events always have velocity so that it never needs to be computed here |
| if (!(QGuiApplicationPrivate::mouseEventCaps(event) & QTouchDevice::Velocity)) { |
| qint64 lastTimestamp = (lastPos.isNull() ? lastPressTime : lastPosTime); |
| if (currentTimestamp == lastTimestamp) |
| return; // events are too close together: velocity would be infinite |
| qreal elapsed = qreal(currentTimestamp - lastTimestamp) / 1000.; |
| velocity = QVector2D(event->localPos() - (lastPos.isNull() ? pressPos : lastPos)) / elapsed; |
| } |
| |
| if (q->yflick()) |
| overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, event); |
| if (q->xflick()) |
| overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, event); |
| |
| drag(currentTimestamp, event->type(), event->localPos(), deltas, overThreshold, false, false, velocity); |
| } |
| |
| 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 = computeCurrentTime(event) - lastPosTime; |
| |
| vData.updateVelocity(); |
| hData.updateVelocity(); |
| |
| draggingEnding(); |
| |
| if (lastPosTime == -1) |
| return; |
| |
| hData.vTime = vData.vTime = timeline.time(); |
| |
| bool canBoost = false; |
| |
| qreal vVelocity = 0; |
| if (elapsed < 100 && vData.velocity != 0.) { |
| vVelocity = (QGuiApplicationPrivate::mouseEventCaps(event) & QTouchDevice::Velocity) |
| ? QGuiApplicationPrivate::mouseEventVelocity(event).y() : vData.velocity; |
| } |
| if ((vData.atBeginning && vVelocity > 0.) || (vData.atEnd && vVelocity < 0.)) { |
| vVelocity /= 2; |
| } else if (vData.continuousFlickVelocity != 0.0 |
| && vData.viewSize/q->height() > QML_FLICK_MULTIFLICK_RATIO |
| && ((vVelocity > 0) == (vData.continuousFlickVelocity > 0)) |
| && qAbs(vVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) { |
| // accelerate flick for large view flicked quickly |
| canBoost = true; |
| } |
| |
| qreal hVelocity = 0; |
| if (elapsed < 100 && hData.velocity != 0.) { |
| hVelocity = (QGuiApplicationPrivate::mouseEventCaps(event) & QTouchDevice::Velocity) |
| ? QGuiApplicationPrivate::mouseEventVelocity(event).x() : hData.velocity; |
| } |
| if ((hData.atBeginning && hVelocity > 0.) || (hData.atEnd && hVelocity < 0.)) { |
| hVelocity /= 2; |
| } else if (hData.continuousFlickVelocity != 0.0 |
| && hData.viewSize/q->width() > QML_FLICK_MULTIFLICK_RATIO |
| && ((hVelocity > 0) == (hData.continuousFlickVelocity > 0)) |
| && qAbs(hVelocity) > QML_FLICK_MULTIFLICK_THRESHOLD) { |
| // accelerate flick for large view flicked quickly |
| canBoost = true; |
| } |
| |
| flickBoost = canBoost ? qBound(1.0, flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0; |
| |
| bool flickedVertically = false; |
| vVelocity *= flickBoost; |
| bool isVerticalFlickAllowed = q->yflick() && qAbs(vVelocity) > MinimumFlickVelocity && qAbs(event->localPos().y() - pressPos.y()) > FlickThreshold; |
| if (isVerticalFlickAllowed) { |
| velocityTimeline.reset(vData.smoothVelocity); |
| vData.smoothVelocity.setValue(-vVelocity); |
| flickedVertically = flickY(vVelocity); |
| } |
| |
| bool flickedHorizontally = false; |
| hVelocity *= flickBoost; |
| bool isHorizontalFlickAllowed = q->xflick() && qAbs(hVelocity) > MinimumFlickVelocity && qAbs(event->localPos().x() - pressPos.x()) > FlickThreshold; |
| if (isHorizontalFlickAllowed) { |
| velocityTimeline.reset(hData.smoothVelocity); |
| hData.smoothVelocity.setValue(-hVelocity); |
| flickedHorizontally = flickX(hVelocity); |
| } |
| |
| if (!isVerticalFlickAllowed) |
| fixupY(); |
| |
| if (!isHorizontalFlickAllowed) |
| fixupX(); |
| |
| flickingStarted(flickedHorizontally, flickedVertically); |
| if (!isViewMoving()) |
| 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) { |
| if (d->delayedPressEvent) { |
| d->replayDelayedPress(); |
| |
| // Now send the release |
| if (window() && window()->mouseGrabberItem()) { |
| QPointF localPos = window()->mouseGrabberItem()->mapFromScene(event->windowPos()); |
| QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); |
| QCoreApplication::sendEvent(window(), mouseEvent.data()); |
| } |
| |
| // And the event has been consumed |
| d->stealMouse = false; |
| d->pressed = false; |
| return; |
| } |
| |
| d->handleMouseReleaseEvent(event); |
| event->accept(); |
| } else { |
| QQuickItem::mouseReleaseEvent(event); |
| } |
| } |
| |
| #if QT_CONFIG(wheelevent) |
| void QQuickFlickable::wheelEvent(QWheelEvent *event) |
| { |
| Q_D(QQuickFlickable); |
| if (!d->interactive) { |
| QQuickItem::wheelEvent(event); |
| return; |
| } |
| event->setAccepted(false); |
| qint64 currentTimestamp = d->computeCurrentTime(event); |
| switch (event->phase()) { |
| case Qt::ScrollBegin: |
| d->scrollingPhase = true; |
| d->accumulatedWheelPixelDelta = QVector2D(); |
| d->vData.velocity = 0; |
| d->hData.velocity = 0; |
| d->timer.start(); |
| d->maybeBeginDrag(currentTimestamp, event->position()); |
| break; |
| case Qt::NoScrollPhase: // default phase with an ordinary wheel mouse |
| case Qt::ScrollUpdate: |
| if (d->scrollingPhase) |
| d->pressed = true; |
| break; |
| case Qt::ScrollMomentum: |
| d->pressed = false; |
| d->scrollingPhase = false; |
| d->draggingEnding(); |
| event->accept(); |
| d->lastPosTime = -1; |
| break; |
| case Qt::ScrollEnd: |
| d->pressed = false; |
| d->scrollingPhase = false; |
| d->draggingEnding(); |
| event->accept(); |
| returnToBounds(); |
| d->lastPosTime = -1; |
| d->stealMouse = false; |
| if (!d->velocityTimeline.isActive() && !d->timeline.isActive()) |
| movementEnding(true, true); |
| return; |
| } |
| |
| if (event->source() == Qt::MouseEventNotSynthesized || event->pixelDelta().isNull()) { |
| // physical mouse wheel, so use angleDelta |
| int xDelta = event->angleDelta().x(); |
| int yDelta = event->angleDelta().y(); |
| if (yflick() && yDelta != 0) { |
| bool valid = false; |
| if (yDelta > 0 && contentY() > -minYExtent()) { |
| d->vData.velocity = qMax(yDelta*2 - d->vData.smoothVelocity.value(), qreal(d->maxVelocity/4)); |
| valid = true; |
| } else if (yDelta < 0 && contentY() < -maxYExtent()) { |
| d->vData.velocity = qMin(yDelta*2 - d->vData.smoothVelocity.value(), qreal(-d->maxVelocity/4)); |
| valid = true; |
| } |
| if (valid) { |
| d->flickY(d->vData.velocity); |
| d->flickingStarted(false, true); |
| if (d->vData.flicking) { |
| d->vMoved = true; |
| movementStarting(); |
| } |
| event->accept(); |
| } |
| } |
| if (xflick() && xDelta != 0) { |
| bool valid = false; |
| if (xDelta > 0 && contentX() > -minXExtent()) { |
| d->hData.velocity = qMax(xDelta*2 - d->hData.smoothVelocity.value(), qreal(d->maxVelocity/4)); |
| valid = true; |
| } else if (xDelta < 0 && contentX() < -maxXExtent()) { |
| d->hData.velocity = qMin(xDelta*2 - d->hData.smoothVelocity.value(), qreal(-d->maxVelocity/4)); |
| valid = true; |
| } |
| if (valid) { |
| d->flickX(d->hData.velocity); |
| d->flickingStarted(true, false); |
| if (d->hData.flicking) { |
| d->hMoved = true; |
| movementStarting(); |
| } |
| event->accept(); |
| } |
| } |
| } else { |
| // use pixelDelta (probably from a trackpad) |
| int xDelta = event->pixelDelta().x(); |
| int yDelta = event->pixelDelta().y(); |
| |
| qreal elapsed = qreal(currentTimestamp - d->lastPosTime) / 1000.; |
| if (elapsed <= 0) { |
| d->lastPosTime = currentTimestamp; |
| return; |
| } |
| QVector2D velocity(xDelta / elapsed, yDelta / elapsed); |
| d->lastPosTime = currentTimestamp; |
| d->accumulatedWheelPixelDelta += QVector2D(event->pixelDelta()); |
| d->drag(currentTimestamp, event->type(), event->position(), d->accumulatedWheelPixelDelta, |
| true, !d->scrollingPhase, true, velocity); |
| event->accept(); |
| } |
| |
| if (!event->isAccepted()) |
| QQuickItem::wheelEvent(event); |
| } |
| #endif |
| |
| bool QQuickFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const |
| { |
| Q_Q(const QQuickFlickable); |
| QQuickItem *item = i; |
| while (item) { |
| QQuickFlickable *flick = qobject_cast<QQuickFlickable*>(item); |
| if (flick && flick->pressDelay() > 0 && flick->isInteractive()) { |
| // Found the innermost flickable with press delay - is it me? |
| return (flick == q); |
| } |
| item = item->parentItem(); |
| } |
| return false; |
| } |
| |
| void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QMouseEvent *event) |
| { |
| Q_Q(QQuickFlickable); |
| if (!q->window() || pressDelay <= 0) |
| return; |
| |
| // Only the innermost flickable should handle the delayed press; this allows |
| // flickables up the parent chain to all see the events in their filter functions |
| if (!isInnermostPressDelay(item)) |
| return; |
| |
| delayedPressEvent = QQuickWindowPrivate::cloneMouseEvent(event); |
| delayedPressEvent->setAccepted(false); |
| delayedPressTimer.start(pressDelay, q); |
| } |
| |
| void QQuickFlickablePrivate::clearDelayedPress() |
| { |
| if (delayedPressEvent) { |
| delayedPressTimer.stop(); |
| delete delayedPressEvent; |
| delayedPressEvent = nullptr; |
| } |
| } |
| |
| void QQuickFlickablePrivate::replayDelayedPress() |
| { |
| Q_Q(QQuickFlickable); |
| if (delayedPressEvent) { |
| // Losing the grab will clear the delayed press event; take control of it here |
| QScopedPointer<QMouseEvent> mouseEvent(delayedPressEvent); |
| delayedPressEvent = nullptr; |
| delayedPressTimer.stop(); |
| |
| // If we have the grab, release before delivering the event |
| if (QQuickWindow *w = q->window()) { |
| QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(w); |
| wpriv->allowChildEventFiltering = false; // don't allow re-filtering during replay |
| replayingPressEvent = true; |
| if (w->mouseGrabberItem() == q) |
| q->ungrabMouse(); |
| |
| // Use the event handler that will take care of finding the proper item to propagate the event |
| QCoreApplication::sendEvent(w, mouseEvent.data()); |
| replayingPressEvent = false; |
| wpriv->allowChildEventFiltering = true; |
| } |
| } |
| } |
| |
| //XXX pixelAligned ignores the global position of the Flickable, i.e. assumes Flickable itself is pixel aligned. |
| void QQuickFlickablePrivate::setViewportX(qreal x) |
| { |
| Q_Q(QQuickFlickable); |
| qreal effectiveX = pixelAligned ? -Round(-x) : x; |
| |
| const qreal maxX = q->maxXExtent(); |
| const qreal minX = q->minXExtent(); |
| |
| if (boundsMovement == int(QQuickFlickable::StopAtBounds)) |
| effectiveX = qBound(maxX, effectiveX, minX); |
| |
| contentItem->setX(effectiveX); |
| if (contentItem->x() != effectiveX) |
| return; // reentered |
| |
| qreal overshoot = 0.0; |
| if (x <= maxX) |
| overshoot = maxX - x; |
| else if (x >= minX) |
| overshoot = minX - x; |
| |
| if (overshoot != hData.overshoot) { |
| hData.overshoot = overshoot; |
| emit q->horizontalOvershootChanged(); |
| } |
| } |
| |
| void QQuickFlickablePrivate::setViewportY(qreal y) |
| { |
| Q_Q(QQuickFlickable); |
| qreal effectiveY = pixelAligned ? -Round(-y) : y; |
| |
| const qreal maxY = q->maxYExtent(); |
| const qreal minY = q->minYExtent(); |
| |
| if (boundsMovement == int(QQuickFlickable::StopAtBounds)) |
| effectiveY = qBound(maxY, effectiveY, minY); |
| |
| contentItem->setY(effectiveY); |
| if (contentItem->y() != effectiveY) |
| return; // reentered |
| |
| qreal overshoot = 0.0; |
| if (y <= maxY) |
| overshoot = maxY - y; |
| else if (y >= minY) |
| overshoot = minY - y; |
| |
| if (overshoot != vData.overshoot) { |
| vData.overshoot = overshoot; |
| emit q->verticalOvershootChanged(); |
| } |
| } |
| |
| void QQuickFlickable::timerEvent(QTimerEvent *event) |
| { |
| Q_D(QQuickFlickable); |
| if (event->timerId() == d->delayedPressTimer.timerId()) { |
| d->delayedPressTimer.stop(); |
| if (d->delayedPressEvent) { |
| d->replayDelayedPress(); |
| } |
| } |
| } |
| |
| 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 qMin<qreal>(minXExtent(), width() - vWidth() - d->hData.endMargin); |
| } |
| /* returns -ve */ |
| qreal QQuickFlickable::maxYExtent() const |
| { |
| Q_D(const QQuickFlickable); |
| return qMin<qreal>(minYExtent(), 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(Qt::Orientations orient) |
| { |
| Q_D(QQuickFlickable); |
| if (orient & Qt::Vertical) |
| d->viewportAxisMoved(d->vData, minYExtent(), maxYExtent(), height(), d->fixupY_callback); |
| if (orient & Qt::Horizontal) |
| d->viewportAxisMoved(d->hData, minXExtent(), maxXExtent(), width(), d->fixupX_callback); |
| d->updateBeginningEnd(); |
| } |
| |
| void QQuickFlickablePrivate::viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, |
| QQuickTimeLineCallback::Callback fixupCallback) |
| { |
| if (!scrollingPhase && (pressed || calcVelocity)) { |
| int elapsed = data.velocityTime.restart(); |
| if (elapsed > 0) { |
| qreal velocity = (data.lastPos - data.move.value()) * 1000 / elapsed; |
| if (qAbs(velocity) > 0) { |
| velocityTimeline.reset(data.smoothVelocity); |
| if (calcVelocity) |
| velocityTimeline.set(data.smoothVelocity, velocity); |
| else |
| velocityTimeline.move(data.smoothVelocity, velocity, reportedVelocitySmoothing); |
| velocityTimeline.move(data.smoothVelocity, 0, reportedVelocitySmoothing); |
| } |
| } |
| } else { |
| if (timeline.time() > data.vTime) { |
| velocityTimeline.reset(data.smoothVelocity); |
| qreal velocity = (data.lastPos - data.move.value()) * 1000 / (timeline.time() - data.vTime); |
| data.smoothVelocity.setValue(velocity); |
| } |
| } |
| |
| if (!data.inOvershoot && !data.fixingUp && data.flicking |
| && (data.move.value() > minExtent || data.move.value() < maxExtent) |
| && qAbs(data.smoothVelocity.value()) > 10) { |
| // Increase deceleration if we've passed a bound |
| qreal overBound = data.move.value() > minExtent |
| ? data.move.value() - minExtent |
| : maxExtent - data.move.value(); |
| data.inOvershoot = true; |
| qreal maxDistance = overShootDistance(vSize) - overBound; |
| resetTimeline(data); |
| if (maxDistance > 0) |
| timeline.accel(data.move, -data.smoothVelocity.value(), deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); |
| timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| } |
| |
| data.lastPos = data.move.value(); |
| data.vTime = timeline.time(); |
| } |
| |
| void QQuickFlickable::geometryChanged(const QRectF &newGeometry, |
| const QRectF &oldGeometry) |
| { |
| Q_D(QQuickFlickable); |
| QQuickItem::geometryChanged(newGeometry, oldGeometry); |
| |
| bool changed = false; |
| if (newGeometry.width() != oldGeometry.width()) { |
| changed = true; // we must update visualArea.widthRatio |
| if (d->hData.viewSize < 0) |
| d->contentItem->setWidth(width()); |
| // 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()) { |
| changed = true; // we must update visualArea.heightRatio |
| if (d->vData.viewSize < 0) |
| d->contentItem->setHeight(height()); |
| // 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(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::Flickable::flick(qreal xVelocity, qreal yVelocity) |
| |
| Flicks the content with \a xVelocity horizontally and \a yVelocity vertically in pixels/sec. |
| |
| Calling this method will update the corresponding moving and flicking properties and signals, |
| just like a real flick. |
| */ |
| |
| void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity) |
| { |
| Q_D(QQuickFlickable); |
| d->hData.reset(); |
| d->vData.reset(); |
| d->hData.velocity = xVelocity; |
| d->vData.velocity = yVelocity; |
| d->hData.vTime = d->vData.vTime = d->timeline.time(); |
| |
| const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(xVelocity); |
| const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(yVelocity); |
| |
| if (flickedX) |
| d->hMoved = true; |
| if (flickedY) |
| d->vMoved = true; |
| movementStarting(); |
| d->flickingStarted(flickedX, flickedY); |
| } |
| |
| void QQuickFlickablePrivate::flickingStarted(bool flickingH, bool flickingV) |
| { |
| Q_Q(QQuickFlickable); |
| if (!flickingH && !flickingV) |
| return; |
| |
| bool wasFlicking = hData.flicking || vData.flicking; |
| if (flickingH && !hData.flicking) { |
| hData.flicking = true; |
| emit q->flickingHorizontallyChanged(); |
| } |
| if (flickingV && !vData.flicking) { |
| vData.flicking = true; |
| emit q->flickingVerticallyChanged(); |
| } |
| if (!wasFlicking && (hData.flicking || vData.flicking)) { |
| emit q->flickingChanged(); |
| emit q->flickStarted(); |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQuick::Flickable::cancelFlick() |
| |
| Cancels the current flick animation. |
| */ |
| |
| void QQuickFlickable::cancelFlick() |
| { |
| Q_D(QQuickFlickable); |
| d->resetTimeline(d->hData); |
| d->resetTimeline(d->vData); |
| movementEnding(); |
| } |
| |
| void QQuickFlickablePrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) |
| { |
| if (QQuickItem *i = qmlobject_cast<QQuickItem *>(o)) { |
| i->setParentItem(static_cast<QQuickFlickablePrivate*>(prop->data)->contentItem); |
| } else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) { |
| static_cast<QQuickFlickablePrivate*>(prop->data)->addPointerHandler(pointerHandler); |
| } else { |
| o->setParent(prop->object); // XXX todo - do we want this? |
| } |
| } |
| |
| int QQuickFlickablePrivate::data_count(QQmlListProperty<QObject> *) |
| { |
| // XXX todo |
| return 0; |
| } |
| |
| QObject *QQuickFlickablePrivate::data_at(QQmlListProperty<QObject> *, int) |
| { |
| // XXX todo |
| return nullptr; |
| } |
| |
| void QQuickFlickablePrivate::data_clear(QQmlListProperty<QObject> *) |
| { |
| // XXX todo |
| } |
| |
| QQmlListProperty<QObject> QQuickFlickable::flickableData() |
| { |
| Q_D(QQuickFlickable); |
| return QQmlListProperty<QObject>(this, (void *)d, QQuickFlickablePrivate::data_append, |
| QQuickFlickablePrivate::data_count, |
| QQuickFlickablePrivate::data_at, |
| QQuickFlickablePrivate::data_clear); |
| } |
| |
| QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren() |
| { |
| Q_D(QQuickFlickable); |
| return QQuickItemPrivate::get(d->contentItem)->children(); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Flickable::boundsBehavior |
| This property holds whether the surface may be dragged |
| beyond the Flickable's boundaries, or overshoot the |
| Flickable's boundaries when flicked. |
| |
| When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value |
| other than \c Flickable.StopAtBounds will give a feeling that the edges of |
| the view are soft, rather than a hard physical boundary. |
| |
| The \c boundsBehavior can be one of: |
| |
| \list |
| \li Flickable.StopAtBounds - the contents can not be dragged beyond the boundary |
| of the flickable, and flicks will not overshoot. |
| \li Flickable.DragOverBounds - the contents can be dragged beyond the boundary |
| of the Flickable, but flicks will not overshoot. |
| \li Flickable.OvershootBounds - the contents can overshoot the boundary when flicked, |
| but the content cannot be dragged beyond the boundary of the flickable. (since \c{QtQuick 2.5}) |
| \li Flickable.DragAndOvershootBounds (default) - the contents can be dragged |
| beyond the boundary of the Flickable, and can overshoot the |
| boundary when flicked. |
| \endlist |
| |
| \sa horizontalOvershoot, verticalOvershoot, boundsMovement |
| */ |
| 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 Transition QtQuick::Flickable::rebound |
| |
| This holds the transition to be applied to the content view when |
| it snaps back to the bounds of the flickable. The transition is |
| triggered when the view is flicked or dragged past the edge of the |
| content area, or when returnToBounds() is called. |
| |
| \qml |
| import QtQuick 2.0 |
| |
| Flickable { |
| width: 150; height: 150 |
| contentWidth: 300; contentHeight: 300 |
| |
| rebound: Transition { |
| NumberAnimation { |
| properties: "x,y" |
| duration: 1000 |
| easing.type: Easing.OutBounce |
| } |
| } |
| |
| Rectangle { |
| width: 300; height: 300 |
| gradient: Gradient { |
| GradientStop { position: 0.0; color: "lightsteelblue" } |
| GradientStop { position: 1.0; color: "blue" } |
| } |
| } |
| } |
| \endqml |
| |
| When the above view is flicked beyond its bounds, it will return to its |
| bounds using the transition specified: |
| |
| \image flickable-rebound.gif |
| |
| If this property is not set, a default animation is applied. |
| */ |
| QQuickTransition *QQuickFlickable::rebound() const |
| { |
| Q_D(const QQuickFlickable); |
| return d->rebound; |
| } |
| |
| void QQuickFlickable::setRebound(QQuickTransition *transition) |
| { |
| Q_D(QQuickFlickable); |
| if (transition) { |
| if (!d->hData.transitionToBounds) |
| d->hData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("x")); |
| if (!d->vData.transitionToBounds) |
| d->vData.transitionToBounds = new QQuickFlickableReboundTransition(this, QLatin1String("y")); |
| } |
| if (d->rebound != transition) { |
| d->rebound = transition; |
| emit reboundChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Flickable::contentWidth |
| \qmlproperty real QtQuick::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 qml/flickable.qml document |
| |
| In some cases, the content dimensions can be automatically set |
| based on the \l {Item::childrenRect.width}{childrenRect.width} |
| and \l {Item::childrenRect.height}{childrenRect.height} properties |
| of the \l contentItem. For example, the previous snippet could be rewritten with: |
| |
| \code |
| contentWidth: contentItem.childrenRect.width; contentHeight: contentItem.childrenRect.height |
| \endcode |
| |
| Though this assumes that the origin of the childrenRect is 0,0. |
| */ |
| 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 QtQuick::Flickable::topMargin |
| \qmlproperty real QtQuick::Flickable::leftMargin |
| \qmlproperty real QtQuick::Flickable::bottomMargin |
| \qmlproperty real QtQuick::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 QtQuick::Flickable::originX |
| \qmlproperty real QtQuick::Flickable::originY |
| |
| These properties hold the origin of the content. This value always refers |
| to the top-left position of the content regardless of layout direction. |
| |
| 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. |
| |
| \sa contentX, contentY |
| */ |
| |
| qreal QQuickFlickable::originY() const |
| { |
| Q_D(const QQuickFlickable); |
| return -minYExtent() + d->vData.startMargin; |
| } |
| |
| qreal QQuickFlickable::originX() const |
| { |
| Q_D(const QQuickFlickable); |
| return -minXExtent() + d->hData.startMargin; |
| } |
| |
| |
| /*! |
| \qmlmethod QtQuick::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); |
| const qreal oldHSize = d->hData.viewSize; |
| const qreal oldVSize = d->vData.viewSize; |
| const bool needToUpdateWidth = w != oldHSize; |
| const bool needToUpdateHeight = h != oldVSize; |
| d->hData.viewSize = w; |
| d->vData.viewSize = h; |
| d->contentItem->setSize(QSizeF(w, h)); |
| if (needToUpdateWidth) |
| emit contentWidthChanged(); |
| if (needToUpdateHeight) |
| emit contentHeightChanged(); |
| |
| if (center.x() != 0) { |
| qreal pos = center.x() * w / oldHSize; |
| setContentX(contentX() + pos - center.x()); |
| } |
| if (center.y() != 0) { |
| qreal pos = center.y() * h / oldVSize; |
| setContentY(contentY() + pos - center.y()); |
| } |
| d->updateBeginningEnd(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::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::AutoFlickIfNeeded) && (vWidth() > width())) |
| return true; |
| if (d->flickableDirection == QQuickFlickable::AutoFlickDirection) |
| return std::floor(qAbs(vWidth() - width())); |
| return d->flickableDirection & QQuickFlickable::HorizontalFlick; |
| } |
| |
| bool QQuickFlickable::yflick() const |
| { |
| Q_D(const QQuickFlickable); |
| if ((d->flickableDirection & QQuickFlickable::AutoFlickIfNeeded) && (vHeight() > height())) |
| return true; |
| if (d->flickableDirection == QQuickFlickable::AutoFlickDirection) |
| return std::floor(qAbs(vHeight() - height())); |
| return d->flickableDirection & QQuickFlickable::VerticalFlick; |
| } |
| |
| void QQuickFlickable::mouseUngrabEvent() |
| { |
| Q_D(QQuickFlickable); |
| // if our mouse grab has been removed (probably by another Flickable), |
| // fix our state |
| if (!d->replayingPressEvent) |
| d->cancelInteraction(); |
| } |
| |
| void QQuickFlickablePrivate::cancelInteraction() |
| { |
| Q_Q(QQuickFlickable); |
| if (pressed) { |
| clearDelayedPress(); |
| pressed = false; |
| draggingEnding(); |
| stealMouse = false; |
| q->setKeepMouseGrab(false); |
| fixupX(); |
| fixupY(); |
| if (!isViewMoving()) |
| q->movementEnding(); |
| } |
| } |
| |
| void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h) |
| { |
| Q_Q(const QQuickFlickable); |
| qCDebug(lcHandlerParent) << "reparenting handler" << h << "to contentItem of" << q; |
| h->setParent(contentItem); |
| QQuickItemPrivate::get(contentItem)->addPointerHandler(h); |
| } |
| |
| /*! |
| QQuickFlickable::filterMouseEvent checks filtered mouse events and potentially steals them. |
| |
| This is how flickable takes over events from other items (\a receiver) that are on top of it. |
| It filters their events and may take over (grab) the \a event. |
| Return true if the mouse event will be stolen. |
| \internal |
| */ |
| bool QQuickFlickable::filterMouseEvent(QQuickItem *receiver, QMouseEvent *event) |
| { |
| Q_D(QQuickFlickable); |
| QPointF localPos = mapFromScene(event->windowPos()); |
| |
| Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself"); |
| if (receiver == this && d->stealMouse) { |
| // we are already the grabber and we do want the mouse event to ourselves. |
| return true; |
| } |
| |
| bool receiverDisabled = receiver && !receiver->isEnabled(); |
| bool stealThisEvent = d->stealMouse; |
| bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); |
| if ((stealThisEvent || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) { |
| QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); |
| mouseEvent->setAccepted(false); |
| |
| switch (mouseEvent->type()) { |
| case QEvent::MouseMove: |
| d->handleMouseMoveEvent(mouseEvent.data()); |
| break; |
| case QEvent::MouseButtonPress: |
| d->handleMousePressEvent(mouseEvent.data()); |
| d->captureDelayedPress(receiver, event); |
| stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above |
| break; |
| case QEvent::MouseButtonRelease: |
| d->handleMouseReleaseEvent(mouseEvent.data()); |
| stealThisEvent = d->stealMouse; |
| break; |
| default: |
| break; |
| } |
| if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) { |
| d->clearDelayedPress(); |
| grabMouse(); |
| } else if (d->delayedPressEvent) { |
| grabMouse(); |
| } |
| |
| const bool filtered = stealThisEvent || d->delayedPressEvent || receiverDisabled; |
| if (filtered) { |
| event->setAccepted(true); |
| } |
| return filtered; |
| } else if (d->lastPosTime != -1) { |
| d->lastPosTime = -1; |
| returnToBounds(); |
| } |
| if (event->type() == QEvent::MouseButtonRelease || (receiverKeepsGrab && !receiverDisabled)) { |
| // mouse released, or another item has claimed the grab |
| d->lastPosTime = -1; |
| d->clearDelayedPress(); |
| d->stealMouse = false; |
| d->pressed = false; |
| } |
| return false; |
| } |
| |
| |
| bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e) |
| { |
| Q_D(QQuickFlickable); |
| if (!isVisible() || !isEnabled() || !isInteractive()) { |
| d->cancelInteraction(); |
| return QQuickItem::childMouseEventFilter(i, e); |
| } |
| |
| switch (e->type()) { |
| case QEvent::MouseButtonPress: |
| case QEvent::MouseMove: |
| case QEvent::MouseButtonRelease: |
| return filterMouseEvent(i, static_cast<QMouseEvent *>(e)); |
| case QEvent::UngrabMouse: |
| if (d->window && d->window->mouseGrabberItem() && d->window->mouseGrabberItem() != this) { |
| // The grab has been taken away from a child and given to some other item. |
| mouseUngrabEvent(); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return QQuickItem::childMouseEventFilter(i, e); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::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 QtQuick::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 QtQuick::Flickable::flicking |
| \qmlproperty bool QtQuick::Flickable::flickingHorizontally |
| \qmlproperty bool QtQuick::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 QtQuick::Flickable::dragging |
| \qmlproperty bool QtQuick::Flickable::draggingHorizontally |
| \qmlproperty bool QtQuick::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); |
| const 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) { |
| if (!hData.dragging && !vData.dragging) { |
| emit q->draggingChanged(); |
| emit q->dragEnded(); |
| } |
| hData.inRebound = false; |
| vData.inRebound = false; |
| } |
| } |
| |
| bool QQuickFlickablePrivate::isViewMoving() const |
| { |
| if (timeline.isActive() |
| || (hData.transitionToBounds && hData.transitionToBounds->isActive()) |
| || (vData.transitionToBounds && vData.transitionToBounds->isActive()) ) { |
| return true; |
| } |
| return false; |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::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 |
| outer Flickables is overridden by the innermost Flickable. If the drag |
| exceeds the platform drag threshold, the press event will be delivered |
| regardless of this property. |
| |
| \sa QStyleHints |
| */ |
| 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 QtQuick::Flickable::moving |
| \qmlproperty bool QtQuick::Flickable::movingHorizontally |
| \qmlproperty bool QtQuick::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::velocityTimelineCompleted() |
| { |
| Q_D(QQuickFlickable); |
| if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive()) |
| || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) { |
| return; |
| } |
| // With subclasses such as GridView, velocityTimeline.completed is emitted repeatedly: |
| // for example setting currentIndex results in a visual "flick" which the user |
| // didn't initiate directly. We don't want to end movement repeatedly, and in |
| // that case movementEnding will happen after the sequence of movements ends. |
| if (d->vData.flicking) |
| movementEnding(); |
| d->updateBeginningEnd(); |
| } |
| |
| void QQuickFlickable::timelineCompleted() |
| { |
| Q_D(QQuickFlickable); |
| if ( (d->hData.transitionToBounds && d->hData.transitionToBounds->isActive()) |
| || (d->vData.transitionToBounds && d->vData.transitionToBounds->isActive()) ) { |
| return; |
| } |
| movementEnding(); |
| d->updateBeginningEnd(); |
| } |
| |
| void QQuickFlickable::movementStarting() |
| { |
| Q_D(QQuickFlickable); |
| bool wasMoving = d->hData.moving || d->vData.moving; |
| if (d->hMoved && !d->hData.moving) { |
| d->hData.moving = true; |
| emit movingHorizontallyChanged(); |
| } |
| if (d->vMoved && !d->vData.moving) { |
| d->vData.moving = true; |
| emit movingVerticallyChanged(); |
| } |
| |
| if (!wasMoving && (d->hData.moving || d->vData.moving)) { |
| emit movingChanged(); |
| emit movementStarted(); |
| } |
| } |
| |
| void QQuickFlickable::movementEnding() |
| { |
| movementEnding(true, true); |
| } |
| |
| void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding) |
| { |
| Q_D(QQuickFlickable); |
| |
| // emit flicking signals |
| bool wasFlicking = d->hData.flicking || d->vData.flicking; |
| if (hMovementEnding && d->hData.flicking) { |
| d->hData.flicking = false; |
| emit flickingHorizontallyChanged(); |
| } |
| if (vMovementEnding && d->vData.flicking) { |
| d->vData.flicking = false; |
| emit flickingVerticallyChanged(); |
| } |
| if (wasFlicking && (!d->hData.flicking || !d->vData.flicking)) { |
| emit flickingChanged(); |
| emit flickEnded(); |
| } |
| |
| // emit moving signals |
| bool wasMoving = isMoving(); |
| if (hMovementEnding && d->hData.moving |
| && (!d->pressed && !d->stealMouse)) { |
| d->hData.moving = false; |
| d->hMoved = false; |
| emit movingHorizontallyChanged(); |
| } |
| if (vMovementEnding && d->vData.moving |
| && (!d->pressed && !d->stealMouse)) { |
| d->vData.moving = false; |
| d->vMoved = false; |
| emit movingVerticallyChanged(); |
| } |
| if (wasMoving && !isMoving()) { |
| emit movingChanged(); |
| emit movementEnded(); |
| } |
| |
| if (hMovementEnding) { |
| d->hData.fixingUp = false; |
| d->hData.smoothVelocity.setValue(0); |
| d->hData.previousDragDelta = 0.0; |
| } |
| if (vMovementEnding) { |
| d->vData.fixingUp = false; |
| d->vData.smoothVelocity.setValue(0); |
| d->vData.previousDragDelta = 0.0; |
| } |
| } |
| |
| void QQuickFlickablePrivate::updateVelocity() |
| { |
| Q_Q(QQuickFlickable); |
| emit q->horizontalVelocityChanged(); |
| emit q->verticalVelocityChanged(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Flickable::horizontalOvershoot |
| \since 5.9 |
| |
| This property holds the horizontal overshoot, that is, the horizontal distance by |
| which the contents has been dragged or flicked past the bounds of the flickable. |
| The value is negative when the content is dragged or flicked beyond the beginning, |
| and positive when beyond the end; \c 0.0 otherwise. |
| |
| Whether the values are reported for dragging and/or flicking is determined by |
| \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement |
| is \c Flickable.StopAtBounds. |
| |
| \sa verticalOvershoot, boundsBehavior, boundsMovement |
| */ |
| qreal QQuickFlickable::horizontalOvershoot() const |
| { |
| Q_D(const QQuickFlickable); |
| return d->hData.overshoot; |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Flickable::verticalOvershoot |
| \since 5.9 |
| |
| This property holds the vertical overshoot, that is, the vertical distance by |
| which the contents has been dragged or flicked past the bounds of the flickable. |
| The value is negative when the content is dragged or flicked beyond the beginning, |
| and positive when beyond the end; \c 0.0 otherwise. |
| |
| Whether the values are reported for dragging and/or flicking is determined by |
| \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement |
| is \c Flickable.StopAtBounds. |
| |
| \sa horizontalOvershoot, boundsBehavior, boundsMovement |
| */ |
| qreal QQuickFlickable::verticalOvershoot() const |
| { |
| Q_D(const QQuickFlickable); |
| return d->vData.overshoot; |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Flickable::boundsMovement |
| \since 5.10 |
| |
| This property holds whether the flickable will give a feeling that the edges of the |
| view are soft, rather than a hard physical boundary. |
| |
| The \c boundsMovement can be one of: |
| |
| \list |
| \li Flickable.StopAtBounds - this allows implementing custom edge effects where the |
| contents do not follow drags or flicks beyond the bounds of the flickable. The values |
| of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom |
| edge effects. |
| \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or |
| flicks beyond the bounds of the flickable is determined by \l boundsBehavior. |
| \endlist |
| |
| The following example keeps the contents within bounds and instead applies a flip |
| effect when flicked over horizontal bounds: |
| \code |
| Flickable { |
| id: flickable |
| boundsMovement: Flickable.StopAtBounds |
| boundsBehavior: Flickable.DragAndOvershootBounds |
| transform: Rotation { |
| axis { x: 0; y: 1; z: 0 } |
| origin.x: flickable.width / 2 |
| origin.y: flickable.height / 2 |
| angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot)) |
| } |
| } |
| \endcode |
| |
| The following example keeps the contents within bounds and instead applies an opacity |
| effect when dragged over vertical bounds: |
| \code |
| Flickable { |
| boundsMovement: Flickable.StopAtBounds |
| boundsBehavior: Flickable.DragOverBounds |
| opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height) |
| } |
| \endcode |
| |
| \sa boundsBehavior, verticalOvershoot, horizontalOvershoot |
| */ |
| QQuickFlickable::BoundsMovement QQuickFlickable::boundsMovement() const |
| { |
| Q_D(const QQuickFlickable); |
| return d->boundsMovement; |
| } |
| |
| void QQuickFlickable::setBoundsMovement(BoundsMovement movement) |
| { |
| Q_D(QQuickFlickable); |
| if (d->boundsMovement == movement) |
| return; |
| |
| d->boundsMovement = movement; |
| emit boundsMovementChanged(); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquickflickable_p.cpp" |