// Commit: d4fa1878ff1e7628d3e984d54f8a93810353c71b /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsgflickable_p.h" #include "qsgflickable_p_p.h" #include "qsgcanvas.h" #include "qsgcanvas_p.h" #include #include #include #include "qplatformdefs.h" QT_BEGIN_NAMESPACE // The maximum number of pixels a flick can overshoot #ifndef QML_FLICK_OVERSHOOT #define QML_FLICK_OVERSHOOT 200 #endif // The number of samples to use in calculating the velocity of a flick #ifndef QML_FLICK_SAMPLEBUFFER #define QML_FLICK_SAMPLEBUFFER 3 #endif // The number of samples to discard when calculating the flick velocity. // Touch panels often produce inaccurate results as the finger is lifted. #ifndef QML_FLICK_DISCARDSAMPLES #define QML_FLICK_DISCARDSAMPLES 1 #endif // The default maximum velocity of a flick. #ifndef QML_FLICK_DEFAULTMAXVELOCITY #define QML_FLICK_DEFAULTMAXVELOCITY 2500 #endif // The default deceleration of a flick. #ifndef QML_FLICK_DEFAULTDECELERATION #define QML_FLICK_DEFAULTDECELERATION 1500 #endif // How much faster to decelerate when overshooting #ifndef QML_FLICK_OVERSHOOTFRICTION #define QML_FLICK_OVERSHOOTFRICTION 8 #endif // FlickThreshold determines how far the "mouse" must have moved // before we perform a flick. static const int FlickThreshold = 20; // RetainGrabVelocity is the maxmimum instantaneous velocity that // will ensure the Flickable retains the grab on consecutive flicks. static const int RetainGrabVelocity = 15; QSGFlickableVisibleArea::QSGFlickableVisibleArea(QSGFlickable *parent) : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.) , m_yPosition(0.), m_heightRatio(0.) { } qreal QSGFlickableVisibleArea::widthRatio() const { return m_widthRatio; } qreal QSGFlickableVisibleArea::xPosition() const { return m_xPosition; } qreal QSGFlickableVisibleArea::heightRatio() const { return m_heightRatio; } qreal QSGFlickableVisibleArea::yPosition() const { return m_yPosition; } void QSGFlickableVisibleArea::updateVisible() { QSGFlickablePrivate *p = QSGFlickablePrivate::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); } QSGFlickablePrivate::QSGFlickablePrivate() : contentItem(new QSGItem) , hData(this, &QSGFlickablePrivate::setViewportX) , vData(this, &QSGFlickablePrivate::setViewportY) , flickingHorizontally(false), flickingVertically(false) , hMoved(false), vMoved(false) , movingHorizontally(false), movingVertically(false) , stealMouse(false), pressed(false), interactive(true), calcVelocity(false) , deceleration(QML_FLICK_DEFAULTDECELERATION) , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100) , delayedPressEvent(0), delayedPressTarget(0), pressDelay(0), fixupDuration(400) , fixupMode(Normal), vTime(0), visibleArea(0) , flickableDirection(QSGFlickable::AutoFlickDirection) , boundsBehavior(QSGFlickable::DragAndOvershootBounds) { } void QSGFlickablePrivate::init() { Q_Q(QSGFlickable); QDeclarative_setParent_noEvent(contentItem, q); contentItem->setParentItem(q); static int timelineUpdatedIdx = -1; static int timelineCompletedIdx = -1; static int flickableTickedIdx = -1; static int flickableMovementEndingIdx = -1; if (timelineUpdatedIdx == -1) { timelineUpdatedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("updated()"); timelineCompletedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("completed()"); flickableTickedIdx = QSGFlickable::staticMetaObject.indexOfSlot("ticked()"); flickableMovementEndingIdx = QSGFlickable::staticMetaObject.indexOfSlot("movementEnding()"); } QMetaObject::connect(&timeline, timelineUpdatedIdx, q, flickableTickedIdx, Qt::DirectConnection); QMetaObject::connect(&timeline, timelineCompletedIdx, q, flickableMovementEndingIdx, Qt::DirectConnection); q->setAcceptedMouseButtons(Qt::LeftButton); q->setFiltersChildMouseEvents(true); QSGItemPrivate *viewportPrivate = QSGItemPrivate::get(contentItem); viewportPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); lastPosTime.invalidate(); } /* Returns the amount to overshoot by given a velocity. Will be roughly in range 0 - size/4 */ qreal QSGFlickablePrivate::overShootDistance(qreal size) { if (maxVelocity <= 0) return 0.0; return qMin(qreal(QML_FLICK_OVERSHOOT), size/3); } void QSGFlickablePrivate::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 QSGFlickablePrivate::AxisData::updateVelocity() { if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) { velocity = 0; int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES; for (int i = 0; i < count; ++i) { qreal v = velocityBuffer.at(i); velocity += v; } velocity /= count; } } void QSGFlickablePrivate::itemGeometryChanged(QSGItem *item, const QRectF &newGeom, const QRectF &oldGeom) { Q_Q(QSGFlickable); if (item == contentItem) { if (newGeom.x() != oldGeom.x()) emit q->contentXChanged(); if (newGeom.y() != oldGeom.y()) emit q->contentYChanged(); } } void QSGFlickablePrivate::flickX(qreal velocity) { Q_Q(QSGFlickable); flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity); } void QSGFlickablePrivate::flickY(qreal velocity) { Q_Q(QSGFlickable); flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity); } void QSGFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal, QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity) { Q_Q(QSGFlickable); qreal maxDistance = -1; data.fixingUp = false; // -ve velocity means list is moving up if (velocity > 0) { maxDistance = qAbs(minExtent - data.move.value()); data.flickTarget = minExtent; } else { maxDistance = qAbs(maxExtent - data.move.value()); data.flickTarget = maxExtent; } if (maxDistance > 0) { qreal v = velocity; if (maxVelocity != -1 && maxVelocity < qAbs(v)) { if (v < 0) v = -maxVelocity; else v = maxVelocity; } timeline.reset(data.move); if (boundsBehavior == QSGFlickable::DragAndOvershootBounds) timeline.accel(data.move, v, deceleration); else timeline.accel(data.move, v, deceleration, maxDistance); timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this)); if (!flickingHorizontally && q->xflick()) { flickingHorizontally = true; emit q->flickingChanged(); emit q->flickingHorizontallyChanged(); if (!flickingVertically) emit q->flickStarted(); } if (!flickingVertically && q->yflick()) { flickingVertically = true; emit q->flickingChanged(); emit q->flickingVerticallyChanged(); if (!flickingHorizontally) emit q->flickStarted(); } } else { timeline.reset(data.move); fixup(data, minExtent, maxExtent); } } void QSGFlickablePrivate::fixupY_callback(void *data) { ((QSGFlickablePrivate *)data)->fixupY(); } void QSGFlickablePrivate::fixupX_callback(void *data) { ((QSGFlickablePrivate *)data)->fixupX(); } void QSGFlickablePrivate::fixupX() { Q_Q(QSGFlickable); fixup(hData, q->minXExtent(), q->maxXExtent()); } void QSGFlickablePrivate::fixupY() { Q_Q(QSGFlickable); fixup(vData, q->minYExtent(), q->maxYExtent()); } void QSGFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) { if (data.move.value() > minExtent || maxExtent > minExtent) { timeline.reset(data.move); if (data.move.value() != minExtent) { switch (fixupMode) { case Immediate: timeline.set(data.move, minExtent); break; case ExtentChanged: // The target has changed. Don't start from the beginning; just complete the // second half of the animation using the new extent. timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); data.fixingUp = true; break; default: { qreal dist = minExtent - data.move; timeline.move(data.move, minExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4); timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); data.fixingUp = true; } } } } else if (data.move.value() < maxExtent) { timeline.reset(data.move); switch (fixupMode) { case Immediate: timeline.set(data.move, maxExtent); break; case ExtentChanged: // The target has changed. Don't start from the beginning; just complete the // second half of the animation using the new extent. timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); data.fixingUp = true; break; default: { qreal dist = maxExtent - data.move; timeline.move(data.move, maxExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4); timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); data.fixingUp = true; } } } data.inOvershoot = false; fixupMode = Normal; vTime = timeline.time(); } void QSGFlickablePrivate::updateBeginningEnd() { Q_Q(QSGFlickable); bool atBoundaryChange = false; // Vertical const int maxyextent = int(-q->maxYExtent()); const qreal ypos = -vData.move.value(); bool atBeginning = (ypos <= -q->minYExtent()); bool atEnd = (maxyextent <= ypos); if (atBeginning != vData.atBeginning) { vData.atBeginning = atBeginning; atBoundaryChange = true; } if (atEnd != vData.atEnd) { vData.atEnd = atEnd; atBoundaryChange = true; } // Horizontal const int maxxextent = int(-q->maxXExtent()); const qreal xpos = -hData.move.value(); atBeginning = (xpos <= -q->minXExtent()); atEnd = (maxxextent <= xpos); if (atBeginning != hData.atBeginning) { hData.atBeginning = atBeginning; atBoundaryChange = true; } if (atEnd != hData.atEnd) { hData.atEnd = atEnd; atBoundaryChange = true; } if (atBoundaryChange) emit q->isAtBoundaryChanged(); if (visibleArea) visibleArea->updateVisible(); } QSGFlickable::QSGFlickable(QSGItem *parent) : QSGItem(*(new QSGFlickablePrivate), parent) { Q_D(QSGFlickable); d->init(); } QSGFlickable::QSGFlickable(QSGFlickablePrivate &dd, QSGItem *parent) : QSGItem(dd, parent) { Q_D(QSGFlickable); d->init(); } QSGFlickable::~QSGFlickable() { } qreal QSGFlickable::contentX() const { Q_D(const QSGFlickable); return -d->contentItem->x(); } void QSGFlickable::setContentX(qreal pos) { Q_D(QSGFlickable); d->timeline.reset(d->hData.move); d->vTime = d->timeline.time(); movementXEnding(); if (-pos != d->hData.move.value()) { d->hData.move.setValue(-pos); viewportMoved(); } } qreal QSGFlickable::contentY() const { Q_D(const QSGFlickable); return -d->contentItem->y(); } void QSGFlickable::setContentY(qreal pos) { Q_D(QSGFlickable); d->timeline.reset(d->vData.move); d->vTime = d->timeline.time(); movementYEnding(); if (-pos != d->vData.move.value()) { d->vData.move.setValue(-pos); viewportMoved(); } } bool QSGFlickable::isInteractive() const { Q_D(const QSGFlickable); return d->interactive; } void QSGFlickable::setInteractive(bool interactive) { Q_D(QSGFlickable); if (interactive != d->interactive) { d->interactive = interactive; if (!interactive && (d->flickingHorizontally || d->flickingVertically)) { d->timeline.clear(); d->vTime = d->timeline.time(); d->flickingHorizontally = false; d->flickingVertically = false; emit flickingChanged(); emit flickingHorizontallyChanged(); emit flickingVerticallyChanged(); emit flickEnded(); } emit interactiveChanged(); } } qreal QSGFlickable::horizontalVelocity() const { Q_D(const QSGFlickable); return d->hData.smoothVelocity.value(); } qreal QSGFlickable::verticalVelocity() const { Q_D(const QSGFlickable); return d->vData.smoothVelocity.value(); } bool QSGFlickable::isAtXEnd() const { Q_D(const QSGFlickable); return d->hData.atEnd; } bool QSGFlickable::isAtXBeginning() const { Q_D(const QSGFlickable); return d->hData.atBeginning; } bool QSGFlickable::isAtYEnd() const { Q_D(const QSGFlickable); return d->vData.atEnd; } bool QSGFlickable::isAtYBeginning() const { Q_D(const QSGFlickable); return d->vData.atBeginning; } void QSGFlickable::ticked() { viewportMoved(); } QSGItem *QSGFlickable::contentItem() { Q_D(QSGFlickable); return d->contentItem; } QSGFlickableVisibleArea *QSGFlickable::visibleArea() { Q_D(QSGFlickable); if (!d->visibleArea) d->visibleArea = new QSGFlickableVisibleArea(this); return d->visibleArea; } QSGFlickable::FlickableDirection QSGFlickable::flickableDirection() const { Q_D(const QSGFlickable); return d->flickableDirection; } void QSGFlickable::setFlickableDirection(FlickableDirection direction) { Q_D(QSGFlickable); if (direction != d->flickableDirection) { d->flickableDirection = direction; emit flickableDirectionChanged(); } } void QSGFlickablePrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event) { Q_Q(QSGFlickable); if (interactive && timeline.isActive() && (qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity || qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity)) { stealMouse = true; // If we've been flicked then steal the click. } else { stealMouse = false; } q->setKeepMouseGrab(stealMouse); pressed = true; timeline.clear(); hData.reset(); vData.reset(); hData.dragMinBound = q->minXExtent(); vData.dragMinBound = q->minYExtent(); hData.dragMaxBound = q->maxXExtent(); vData.dragMaxBound = q->maxYExtent(); fixupMode = Normal; lastPos = QPoint(); QSGItemPrivate::start(lastPosTime); pressPos = event->pos(); hData.pressPos = hData.move.value(); vData.pressPos = vData.move.value(); flickingHorizontally = false; flickingVertically = false; QSGItemPrivate::start(pressTime); QSGItemPrivate::start(velocityTime); } void QSGFlickablePrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event) { Q_Q(QSGFlickable); if (!interactive || !lastPosTime.isValid()) return; bool rejectY = false; bool rejectX = false; bool stealY = stealMouse; bool stealX = stealMouse; if (q->yflick()) { int dy = int(event->pos().y() - pressPos.y()); if (qAbs(dy) > QApplication::startDragDistance() || QSGItemPrivate::elapsed(pressTime) > 200) { if (!vMoved) vData.dragStartOffset = dy; qreal newY = dy + vData.pressPos - vData.dragStartOffset; const qreal minY = vData.dragMinBound; const qreal maxY = vData.dragMaxBound; if (newY > minY) newY = minY + (newY - minY) / 2; if (newY < maxY && maxY - minY <= 0) newY = maxY + (newY - maxY) / 2; if (boundsBehavior == QSGFlickable::StopAtBounds && (newY > minY || newY < maxY)) { rejectY = true; if (newY < maxY) { newY = maxY; rejectY = false; } if (newY > minY) { newY = minY; rejectY = false; } } if (!rejectY && stealMouse) { vData.move.setValue(qRound(newY)); vMoved = true; } if (qAbs(dy) > QApplication::startDragDistance()) stealY = true; } } if (q->xflick()) { int dx = int(event->pos().x() - pressPos.x()); if (qAbs(dx) > QApplication::startDragDistance() || QSGItemPrivate::elapsed(pressTime) > 200) { if (!hMoved) hData.dragStartOffset = dx; qreal newX = dx + hData.pressPos - hData.dragStartOffset; const qreal minX = hData.dragMinBound; const qreal maxX = hData.dragMaxBound; if (newX > minX) newX = minX + (newX - minX) / 2; if (newX < maxX && maxX - minX <= 0) newX = maxX + (newX - maxX) / 2; if (boundsBehavior == QSGFlickable::StopAtBounds && (newX > minX || newX < maxX)) { rejectX = true; if (newX < maxX) { newX = maxX; rejectX = false; } if (newX > minX) { newX = minX; rejectX = false; } } if (!rejectX && stealMouse) { hData.move.setValue(qRound(newX)); hMoved = true; } if (qAbs(dx) > QApplication::startDragDistance()) stealX = true; } } stealMouse = stealX || stealY; if (stealMouse) q->setKeepMouseGrab(true); if (rejectY) { vData.velocityBuffer.clear(); vData.velocity = 0; } if (rejectX) { hData.velocityBuffer.clear(); hData.velocity = 0; } if (hMoved || vMoved) { q->movementStarting(); q->viewportMoved(); } if (!lastPos.isNull()) { qreal elapsed = qreal(QSGItemPrivate::elapsed(lastPosTime)) / 1000.; if (elapsed <= 0) return; QSGItemPrivate::restart(lastPosTime); qreal dy = event->pos().y()-lastPos.y(); if (q->yflick() && !rejectY) vData.addVelocitySample(dy/elapsed, maxVelocity); qreal dx = event->pos().x()-lastPos.x(); if (q->xflick() && !rejectX) hData.addVelocitySample(dx/elapsed, maxVelocity); } lastPos = event->pos(); } void QSGFlickablePrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_Q(QSGFlickable); stealMouse = false; q->setKeepMouseGrab(false); pressed = false; if (!lastPosTime.isValid()) return; // if we drag then pause before release we should not cause a flick. if (QSGItemPrivate::elapsed(lastPosTime) < 100) { vData.updateVelocity(); hData.updateVelocity(); } else { hData.velocity = 0.0; vData.velocity = 0.0; } vTime = timeline.time(); qreal velocity = vData.velocity; if (vData.atBeginning || vData.atEnd) velocity /= 2; if (qAbs(velocity) > MinimumFlickVelocity && qAbs(event->pos().y() - pressPos.y()) > FlickThreshold) flickY(velocity); else fixupY(); velocity = hData.velocity; if (hData.atBeginning || hData.atEnd) velocity /= 2; if (qAbs(velocity) > MinimumFlickVelocity && qAbs(event->pos().x() - pressPos.x()) > FlickThreshold) flickX(velocity); else fixupX(); if (!timeline.isActive()) q->movementEnding(); } void QSGFlickable::mousePressEvent(QGraphicsSceneMouseEvent *event) { Q_D(QSGFlickable); if (d->interactive) { if (!d->pressed) d->handleMousePressEvent(event); event->accept(); } else { QSGItem::mousePressEvent(event); } } void QSGFlickable::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { Q_D(QSGFlickable); if (d->interactive) { d->handleMouseMoveEvent(event); event->accept(); } else { QSGItem::mouseMoveEvent(event); } } void QSGFlickable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_D(QSGFlickable); if (d->interactive) { d->clearDelayedPress(); d->handleMouseReleaseEvent(event); event->accept(); ungrabMouse(); } else { QSGItem::mouseReleaseEvent(event); } } void QSGFlickable::wheelEvent(QGraphicsSceneWheelEvent *event) { Q_D(QSGFlickable); if (!d->interactive) { QSGItem::wheelEvent(event); } else if (yflick() && event->orientation() == Qt::Vertical) { bool valid = false; if (event->delta() > 0 && contentY() > -minYExtent()) { d->vData.velocity = qMax(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(d->maxVelocity/4)); valid = true; } else if (event->delta() < 0 && contentY() < -maxYExtent()) { d->vData.velocity = qMin(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(-d->maxVelocity/4)); valid = true; } if (valid) { d->flickingVertically = false; d->flickY(d->vData.velocity); if (d->flickingVertically) { d->vMoved = true; movementStarting(); } event->accept(); } } else if (xflick() && event->orientation() == Qt::Horizontal) { bool valid = false; if (event->delta() > 0 && contentX() > -minXExtent()) { d->hData.velocity = qMax(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(d->maxVelocity/4)); valid = true; } else if (event->delta() < 0 && contentX() < -maxXExtent()) { d->hData.velocity = qMin(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(-d->maxVelocity/4)); valid = true; } if (valid) { d->flickingHorizontally = false; d->flickX(d->hData.velocity); if (d->flickingHorizontally) { d->hMoved = true; movementStarting(); } event->accept(); } } else { QSGItem::wheelEvent(event); } } bool QSGFlickablePrivate::isOutermostPressDelay() const { Q_Q(const QSGFlickable); QSGItem *item = q->parentItem(); while (item) { QSGFlickable *flick = qobject_cast(item); if (flick && flick->pressDelay() > 0 && flick->isInteractive()) return false; item = item->parentItem(); } return true; } void QSGFlickablePrivate::captureDelayedPress(QGraphicsSceneMouseEvent *event) { Q_Q(QSGFlickable); if (!q->canvas() || pressDelay <= 0) return; if (!isOutermostPressDelay()) return; delayedPressTarget = q->canvas()->mouseGrabberItem(); delayedPressEvent = new QGraphicsSceneMouseEvent(event->type()); delayedPressEvent->setAccepted(false); for (int i = 0x1; i <= 0x10; i <<= 1) { if (event->buttons() & i) { Qt::MouseButton button = Qt::MouseButton(i); delayedPressEvent->setButtonDownPos(button, event->buttonDownPos(button)); delayedPressEvent->setButtonDownScenePos(button, event->buttonDownScenePos(button)); delayedPressEvent->setButtonDownScreenPos(button, event->buttonDownScreenPos(button)); } } delayedPressEvent->setButtons(event->buttons()); delayedPressEvent->setButton(event->button()); delayedPressEvent->setPos(event->pos()); delayedPressEvent->setScenePos(event->scenePos()); delayedPressEvent->setScreenPos(event->screenPos()); delayedPressEvent->setLastPos(event->lastPos()); delayedPressEvent->setLastScenePos(event->lastScenePos()); delayedPressEvent->setLastScreenPos(event->lastScreenPos()); delayedPressEvent->setModifiers(event->modifiers()); delayedPressTimer.start(pressDelay, q); } void QSGFlickablePrivate::clearDelayedPress() { if (delayedPressEvent) { delayedPressTimer.stop(); delete delayedPressEvent; delayedPressEvent = 0; } } void QSGFlickablePrivate::setViewportX(qreal x) { contentItem->setX(x); } void QSGFlickablePrivate::setViewportY(qreal y) { contentItem->setY(y); } void QSGFlickable::timerEvent(QTimerEvent *event) { Q_D(QSGFlickable); if (event->timerId() == d->delayedPressTimer.timerId()) { d->delayedPressTimer.stop(); if (d->delayedPressEvent) { QSGItem *grabber = canvas() ? canvas()->mouseGrabberItem() : 0; if (!grabber || grabber != this) { // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay) // so we reset the grabber if (canvas()->mouseGrabberItem() == d->delayedPressTarget) d->delayedPressTarget->ungrabMouse(); // Use the event handler that will take care of finding the proper item to propagate the event QSGCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent); } delete d->delayedPressEvent; d->delayedPressEvent = 0; } } } qreal QSGFlickable::minYExtent() const { return 0.0; } qreal QSGFlickable::minXExtent() const { return 0.0; } /* returns -ve */ qreal QSGFlickable::maxXExtent() const { return width() - vWidth(); } /* returns -ve */ qreal QSGFlickable::maxYExtent() const { return height() - vHeight(); } void QSGFlickable::viewportMoved() { Q_D(QSGFlickable); qreal prevX = d->lastFlickablePosition.x(); qreal prevY = d->lastFlickablePosition.y(); d->velocityTimeline.clear(); if (d->pressed || d->calcVelocity) { int elapsed = QSGItemPrivate::restart(d->velocityTime); if (elapsed > 0) { qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / elapsed; qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / elapsed; d->velocityTimeline.move(d->hData.smoothVelocity, horizontalVelocity, d->reportedVelocitySmoothing); d->velocityTimeline.move(d->hData.smoothVelocity, 0, d->reportedVelocitySmoothing); d->velocityTimeline.move(d->vData.smoothVelocity, verticalVelocity, d->reportedVelocitySmoothing); d->velocityTimeline.move(d->vData.smoothVelocity, 0, d->reportedVelocitySmoothing); } } else { if (d->timeline.time() > d->vTime) { qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / (d->timeline.time() - d->vTime); qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / (d->timeline.time() - d->vTime); d->hData.smoothVelocity.setValue(horizontalVelocity); d->vData.smoothVelocity.setValue(verticalVelocity); } } if (!d->vData.inOvershoot && !d->vData.fixingUp && d->flickingVertically && (d->vData.move.value() > minYExtent() || d->vData.move.value() < maxYExtent()) && qAbs(d->vData.smoothVelocity.value()) > 100) { // Increase deceleration if we've passed a bound d->vData.inOvershoot = true; qreal maxDistance = d->overShootDistance(height()); d->timeline.reset(d->vData.move); d->timeline.accel(d->vData.move, -d->vData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); d->timeline.callback(QDeclarativeTimeLineCallback(&d->vData.move, d->fixupY_callback, d)); } if (!d->hData.inOvershoot && !d->hData.fixingUp && d->flickingHorizontally && (d->hData.move.value() > minXExtent() || d->hData.move.value() < maxXExtent()) && qAbs(d->hData.smoothVelocity.value()) > 100) { // Increase deceleration if we've passed a bound d->hData.inOvershoot = true; qreal maxDistance = d->overShootDistance(width()); d->timeline.reset(d->hData.move); d->timeline.accel(d->hData.move, -d->hData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); d->timeline.callback(QDeclarativeTimeLineCallback(&d->hData.move, d->fixupX_callback, d)); } d->lastFlickablePosition = QPointF(d->hData.move.value(), d->vData.move.value()); d->vTime = d->timeline.time(); d->updateBeginningEnd(); } void QSGFlickable::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QSGFlickable); QSGItem::geometryChanged(newGeometry, oldGeometry); bool changed = false; if (newGeometry.width() != oldGeometry.width()) { if (xflick()) changed = true; if (d->hData.viewSize < 0) { d->contentItem->setWidth(width()); emit contentWidthChanged(); } // Make sure that we're entirely in view. if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { d->fixupMode = QSGFlickablePrivate::Immediate; d->fixupX(); } } if (newGeometry.height() != oldGeometry.height()) { if (yflick()) changed = true; if (d->vData.viewSize < 0) { d->contentItem->setHeight(height()); emit contentHeightChanged(); } // Make sure that we're entirely in view. if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { d->fixupMode = QSGFlickablePrivate::Immediate; d->fixupY(); } } if (changed) d->updateBeginningEnd(); } void QSGFlickable::cancelFlick() { Q_D(QSGFlickable); d->timeline.reset(d->hData.move); d->timeline.reset(d->vData.move); movementEnding(); } void QSGFlickablePrivate::data_append(QDeclarativeListProperty *prop, QObject *o) { QSGItem *i = qobject_cast(o); if (i) { i->setParentItem(static_cast(prop->data)->contentItem); } else { o->setParent(prop->object); // XXX todo - do we want this? } } int QSGFlickablePrivate::data_count(QDeclarativeListProperty *) { // XXX todo return 0; } QObject *QSGFlickablePrivate::data_at(QDeclarativeListProperty *, int) { // XXX todo return 0; } void QSGFlickablePrivate::data_clear(QDeclarativeListProperty *) { // XXX todo } QDeclarativeListProperty QSGFlickable::flickableData() { Q_D(QSGFlickable); return QDeclarativeListProperty(this, (void *)d, QSGFlickablePrivate::data_append, QSGFlickablePrivate::data_count, QSGFlickablePrivate::data_at, QSGFlickablePrivate::data_clear); } QDeclarativeListProperty QSGFlickable::flickableChildren() { Q_D(QSGFlickable); return QSGItemPrivate::get(d->contentItem)->children(); } QSGFlickable::BoundsBehavior QSGFlickable::boundsBehavior() const { Q_D(const QSGFlickable); return d->boundsBehavior; } void QSGFlickable::setBoundsBehavior(BoundsBehavior b) { Q_D(QSGFlickable); if (b == d->boundsBehavior) return; d->boundsBehavior = b; emit boundsBehaviorChanged(); } qreal QSGFlickable::contentWidth() const { Q_D(const QSGFlickable); return d->hData.viewSize; } void QSGFlickable::setContentWidth(qreal w) { Q_D(QSGFlickable); if (d->hData.viewSize == w) return; d->hData.viewSize = w; if (w < 0) d->contentItem->setWidth(width()); else d->contentItem->setWidth(w); // Make sure that we're entirely in view. if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { d->fixupMode = QSGFlickablePrivate::Immediate; d->fixupX(); } else if (!d->pressed && d->hData.fixingUp) { d->fixupMode = QSGFlickablePrivate::ExtentChanged; d->fixupX(); } emit contentWidthChanged(); d->updateBeginningEnd(); } qreal QSGFlickable::contentHeight() const { Q_D(const QSGFlickable); return d->vData.viewSize; } void QSGFlickable::setContentHeight(qreal h) { Q_D(QSGFlickable); if (d->vData.viewSize == h) return; d->vData.viewSize = h; if (h < 0) d->contentItem->setHeight(height()); else d->contentItem->setHeight(h); // Make sure that we're entirely in view. if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { d->fixupMode = QSGFlickablePrivate::Immediate; d->fixupY(); } else if (!d->pressed && d->vData.fixingUp) { d->fixupMode = QSGFlickablePrivate::ExtentChanged; d->fixupY(); } emit contentHeightChanged(); d->updateBeginningEnd(); } void QSGFlickable::resizeContent(qreal w, qreal h, QPointF center) { Q_D(QSGFlickable); if (w != d->hData.viewSize) { qreal oldSize = d->hData.viewSize; d->hData.viewSize = w; d->contentItem->setWidth(w); emit contentWidthChanged(); if (center.x() != 0) { qreal pos = center.x() * w / oldSize; setContentX(contentX() + pos - center.x()); } } if (h != d->vData.viewSize) { qreal oldSize = d->vData.viewSize; d->vData.viewSize = h; d->contentItem->setHeight(h); emit contentHeightChanged(); if (center.y() != 0) { qreal pos = center.y() * h / oldSize; setContentY(contentY() + pos - center.y()); } } d->updateBeginningEnd(); } void QSGFlickable::returnToBounds() { Q_D(QSGFlickable); d->fixupX(); d->fixupY(); } qreal QSGFlickable::vWidth() const { Q_D(const QSGFlickable); if (d->hData.viewSize < 0) return width(); else return d->hData.viewSize; } qreal QSGFlickable::vHeight() const { Q_D(const QSGFlickable); if (d->vData.viewSize < 0) return height(); else return d->vData.viewSize; } bool QSGFlickable::xflick() const { Q_D(const QSGFlickable); if (d->flickableDirection == QSGFlickable::AutoFlickDirection) return vWidth() != width(); return d->flickableDirection & QSGFlickable::HorizontalFlick; } bool QSGFlickable::yflick() const { Q_D(const QSGFlickable); if (d->flickableDirection == QSGFlickable::AutoFlickDirection) return vHeight() != height(); return d->flickableDirection & QSGFlickable::VerticalFlick; } void QSGFlickable::mouseUngrabEvent() { Q_D(QSGFlickable); if (d->pressed) { // if our mouse grab has been removed (probably by another Flickable), // fix our state d->pressed = false; d->stealMouse = false; setKeepMouseGrab(false); } } bool QSGFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) { Q_D(QSGFlickable); QGraphicsSceneMouseEvent mouseEvent(event->type()); QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); QSGCanvas *c = canvas(); QSGItem *grabber = c ? c->mouseGrabberItem() : 0; bool disabledItem = grabber && !grabber->isEnabled(); bool stealThisEvent = d->stealMouse; if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { mouseEvent.setAccepted(false); for (int i = 0x1; i <= 0x10; i <<= 1) { if (event->buttons() & i) { Qt::MouseButton button = Qt::MouseButton(i); mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button))); } } mouseEvent.setScenePos(event->scenePos()); mouseEvent.setLastScenePos(event->lastScenePos()); mouseEvent.setPos(mapFromScene(event->scenePos())); mouseEvent.setLastPos(mapFromScene(event->lastScenePos())); switch(mouseEvent.type()) { case QEvent::GraphicsSceneMouseMove: d->handleMouseMoveEvent(&mouseEvent); break; case QEvent::GraphicsSceneMousePress: if (d->pressed) // we are already pressed - this is a delayed replay return false; d->handleMousePressEvent(&mouseEvent); d->captureDelayedPress(event); stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above break; case QEvent::GraphicsSceneMouseRelease: if (d->delayedPressEvent) { // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay) // so we reset the grabber if (c->mouseGrabberItem() == d->delayedPressTarget) d->delayedPressTarget->ungrabMouse(); //Use the event handler that will take care of finding the proper item to propagate the event QSGCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent); d->clearDelayedPress(); // We send the release canvas()->sendEvent(c->mouseGrabberItem(), event); // And the event has been consumed d->stealMouse = false; d->pressed = false; return true; } d->handleMouseReleaseEvent(&mouseEvent); break; default: break; } grabber = qobject_cast(c->mouseGrabberItem()); if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || disabledItem) { d->clearDelayedPress(); grabMouse(); } return stealThisEvent || d->delayedPressEvent || disabledItem; } else if (d->lastPosTime.isValid()) { d->lastPosTime.invalidate(); } if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) { d->clearDelayedPress(); d->stealMouse = false; d->pressed = false; } return false; } bool QSGFlickable::childMouseEventFilter(QSGItem *i, QEvent *e) { Q_D(QSGFlickable); if (!isVisible() || !d->interactive) return QSGItem::childMouseEventFilter(i, e); switch (e->type()) { case QEvent::GraphicsSceneMousePress: case QEvent::GraphicsSceneMouseMove: case QEvent::GraphicsSceneMouseRelease: return sendMouseEvent(static_cast(e)); default: break; } return QSGItem::childMouseEventFilter(i, e); } qreal QSGFlickable::maximumFlickVelocity() const { Q_D(const QSGFlickable); return d->maxVelocity; } void QSGFlickable::setMaximumFlickVelocity(qreal v) { Q_D(QSGFlickable); if (v == d->maxVelocity) return; d->maxVelocity = v; emit maximumFlickVelocityChanged(); } qreal QSGFlickable::flickDeceleration() const { Q_D(const QSGFlickable); return d->deceleration; } void QSGFlickable::setFlickDeceleration(qreal deceleration) { Q_D(QSGFlickable); if (deceleration == d->deceleration) return; d->deceleration = deceleration; emit flickDecelerationChanged(); } bool QSGFlickable::isFlicking() const { Q_D(const QSGFlickable); return d->flickingHorizontally || d->flickingVertically; } bool QSGFlickable::isFlickingHorizontally() const { Q_D(const QSGFlickable); return d->flickingHorizontally; } bool QSGFlickable::isFlickingVertically() const { Q_D(const QSGFlickable); return d->flickingVertically; } int QSGFlickable::pressDelay() const { Q_D(const QSGFlickable); return d->pressDelay; } void QSGFlickable::setPressDelay(int delay) { Q_D(QSGFlickable); if (d->pressDelay == delay) return; d->pressDelay = delay; emit pressDelayChanged(); } bool QSGFlickable::isMoving() const { Q_D(const QSGFlickable); return d->movingHorizontally || d->movingVertically; } bool QSGFlickable::isMovingHorizontally() const { Q_D(const QSGFlickable); return d->movingHorizontally; } bool QSGFlickable::isMovingVertically() const { Q_D(const QSGFlickable); return d->movingVertically; } void QSGFlickable::movementStarting() { Q_D(QSGFlickable); if (d->hMoved && !d->movingHorizontally) { d->movingHorizontally = true; emit movingChanged(); emit movingHorizontallyChanged(); if (!d->movingVertically) emit movementStarted(); } else if (d->vMoved && !d->movingVertically) { d->movingVertically = true; emit movingChanged(); emit movingVerticallyChanged(); if (!d->movingHorizontally) emit movementStarted(); } } void QSGFlickable::movementEnding() { Q_D(QSGFlickable); movementXEnding(); movementYEnding(); d->hData.smoothVelocity.setValue(0); d->vData.smoothVelocity.setValue(0); } void QSGFlickable::movementXEnding() { Q_D(QSGFlickable); if (d->flickingHorizontally) { d->flickingHorizontally = false; emit flickingChanged(); emit flickingHorizontallyChanged(); if (!d->flickingVertically) emit flickEnded(); } if (!d->pressed && !d->stealMouse) { if (d->movingHorizontally) { d->movingHorizontally = false; d->hMoved = false; emit movingChanged(); emit movingHorizontallyChanged(); if (!d->movingVertically) emit movementEnded(); } } d->hData.fixingUp = false; } void QSGFlickable::movementYEnding() { Q_D(QSGFlickable); if (d->flickingVertically) { d->flickingVertically = false; emit flickingChanged(); emit flickingVerticallyChanged(); if (!d->flickingHorizontally) emit flickEnded(); } if (!d->pressed && !d->stealMouse) { if (d->movingVertically) { d->movingVertically = false; d->vMoved = false; emit movingChanged(); emit movingVerticallyChanged(); if (!d->movingHorizontally) emit movementEnded(); } } d->vData.fixingUp = false; } void QSGFlickablePrivate::updateVelocity() { Q_Q(QSGFlickable); emit q->horizontalVelocityChanged(); emit q->verticalVelocityChanged(); } QT_END_NAMESPACE