diff options
Diffstat (limited to 'src/quick')
-rw-r--r-- | src/quick/items/qquickflickable.cpp | 284 | ||||
-rw-r--r-- | src/quick/items/qquickflickable_p_p.h | 11 | ||||
-rw-r--r-- | src/quick/items/qquickwindow.cpp | 2 |
3 files changed, 214 insertions, 83 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 39d47d7254..acdcb6d343 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -59,6 +59,10 @@ static const int FlickThreshold = 15; // will ensure the Flickable retains the grab on consecutive flicks. static const int RetainGrabVelocity = 100; +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.) @@ -212,7 +216,8 @@ QQuickFlickablePrivate::QQuickFlickablePrivate() , hData(this, &QQuickFlickablePrivate::setViewportX) , vData(this, &QQuickFlickablePrivate::setViewportY) , hMoved(false), vMoved(false) - , stealMouse(false), pressed(false), interactive(true), calcVelocity(false) + , stealMouse(false), pressed(false) + , scrollingPhase(false), interactive(true), calcVelocity(false) , pixelAligned(false), replayingPressEvent(false) , lastPosTime(-1) , lastPressTime(0) @@ -915,9 +920,16 @@ qint64 QQuickFlickablePrivate::computeCurrentTime(QInputEvent *event) { if (0 != event->timestamp()) return event->timestamp(); + if (!timer.isValid()) + return 0LL; return timer.elapsed(); } +qreal QQuickFlickablePrivate::devicePixelRatio() +{ + return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio()); +} + void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) { Q_Q(QQuickFlickable); @@ -945,6 +957,13 @@ void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) 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; @@ -965,7 +984,7 @@ void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) vData.dragMaxBound = q->maxYExtent() + vData.endMargin; fixupMode = Normal; lastPos = QPointF(); - pressPos = event->localPos(); + pressPos = pressPosn; hData.pressPos = hData.move.value(); vData.pressPos = vData.move.value(); bool wasFlicking = hData.flicking || vData.flicking; @@ -979,29 +998,33 @@ void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) } if (wasFlicking) emit q->flickingChanged(); - lastPosTime = lastPressTime = computeCurrentTime(event); + lastPosTime = lastPressTime = currentTimestamp; vData.velocityTime.start(); hData.velocityTime.start(); } -void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) +void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, + const QVector2D &deltas, bool overThreshold, bool momentum, const QVector2D &velocity) { Q_Q(QQuickFlickable); - if (!interactive || lastPosTime == -1) - return; bool rejectY = false; bool rejectX = false; - bool stealY = stealMouse; - bool stealX = stealMouse; + 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 = computeCurrentTime(event) - lastPressTime; + qint64 elapsedSincePress = currentTimestamp - lastPressTime; + if (q->yflick()) { - qreal dy = event->localPos().y() - pressPos.y(); - bool overThreshold = QQuickWindowPrivate::dragOverThreshold(dy, Qt::YAxis, event); + qreal dy = deltas.y(); if (overThreshold || elapsedSincePress > 200) { if (!vMoved) vData.dragStartOffset = dy; @@ -1021,12 +1044,38 @@ void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) rejectY = vData.pressPos == minY && vData.move.value() == minY && dy > 0; } } else { - if (newY > minY) - newY = minY + (newY - minY) / 2; - if (newY < maxY && maxY - minY <= 0) - newY = maxY + (newY - maxY) / 2; + 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; + } + 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 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; + } + 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; + } } - if (!rejectY && stealMouse && dy != 0.0) { + if (!rejectY && stealMouse && dy != 0.0 && dy != vData.previousDragDelta) { clearTimeline(); vData.move.setValue(newY); vMoved = true; @@ -1034,11 +1083,11 @@ void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) if (!rejectY && overThreshold) stealY = true; } + vData.previousDragDelta = dy; } if (q->xflick()) { - qreal dx = event->localPos().x() - pressPos.x(); - bool overThreshold = QQuickWindowPrivate::dragOverThreshold(dx, Qt::XAxis, event); + qreal dx = deltas.x(); if (overThreshold || elapsedSincePress > 200) { if (!hMoved) hData.dragStartOffset = dx; @@ -1055,13 +1104,39 @@ void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) rejectX = hData.pressPos == minX && hData.move.value() == minX && dx > 0; } } else { - if (newX > minX) - newX = minX + (newX - minX) / 2; - if (newX < maxX && maxX - minX <= 0) - newX = maxX + (newX - maxX) / 2; + 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; + } + 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 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; + } + 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; + } } - if (!rejectX && stealMouse && dx != 0.0) { + if (!rejectX && stealMouse && dx != 0.0 && dx != hData.previousDragDelta) { clearTimeline(); hData.move.setValue(newX); hMoved = true; @@ -1070,6 +1145,7 @@ void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) if (!rejectX && overThreshold) stealX = true; } + hData.previousDragDelta = dx; } stealMouse = stealX || stealY; @@ -1092,29 +1168,35 @@ void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) 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) + return; + qint64 currentTimestamp = computeCurrentTime(event); qreal elapsed = qreal(currentTimestamp - (lastPos.isNull() ? lastPressTime : lastPosTime)) / 1000.; - if (elapsed <= 0) - return; - lastPosTime = currentTimestamp; - if (q->yflick() && !rejectY) { - if (QGuiApplicationPrivate::mouseEventCaps(event) & QTouchDevice::Velocity) { - vData.addVelocitySample(QGuiApplicationPrivate::mouseEventVelocity(event).y(), maxVelocity); - } else { - qreal dy = event->localPos().y() - (lastPos.isNull() ? pressPos.y() : lastPos.y()); - vData.addVelocitySample(dy/elapsed, maxVelocity); - } - } - if (q->xflick() && !rejectX) { - if (QGuiApplicationPrivate::mouseEventCaps(event) & QTouchDevice::Velocity) { - hData.addVelocitySample(QGuiApplicationPrivate::mouseEventVelocity(event).x(), maxVelocity); - } else { - qreal dx = event->localPos().x() - (lastPos.isNull() ? pressPos.x() : lastPos.x()); - hData.addVelocitySample(dx/elapsed, maxVelocity); - } - } + 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)) + velocity = QVector2D(event->localPos() - (lastPos.isNull() ? pressPos : lastPos)) / elapsed; - lastPos = event->localPos(); + 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, velocity); } void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event) @@ -1258,50 +1340,86 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) 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->posF()); + break; + case Qt::ScrollUpdate: + break; + case Qt::ScrollEnd: + d->scrollingPhase = false; + d->draggingEnding(); + event->accept(); + returnToBounds(); + d->lastPosTime = -1; + return; + } - int yDelta = event->angleDelta().y(); - int xDelta = event->angleDelta().x(); - 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->vData.flicking = false; - d->flickY(d->vData.velocity); - d->flickingStarted(false, true); - if (d->vData.flicking) { - d->vMoved = true; - movementStarting(); + 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->vData.flicking = false; + d->flickY(d->vData.velocity); + d->flickingStarted(false, true); + if (d->vData.flicking) { + d->vMoved = true; + movementStarting(); + } + event->accept(); } - 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->hData.flicking = false; - d->flickX(d->hData.velocity); - d->flickingStarted(true, false); - if (d->hData.flicking) { - d->hMoved = true; - movementStarting(); + 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; } - event->accept(); + if (valid) { + d->hData.flicking = false; + 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->posF(), d->accumulatedWheelPixelDelta, true, !d->scrollingPhase, velocity); } if (!event->isAccepted()) @@ -2240,6 +2358,8 @@ void QQuickFlickablePrivate::draggingEnding() emit q->draggingChanged(); emit q->dragEnded(); } + hData.inRebound = false; + vData.inRebound = false; } bool QQuickFlickablePrivate::isViewMoving() const @@ -2389,10 +2509,12 @@ void QQuickFlickable::movementEnding(bool hMovementEnding, bool vMovementEnding) 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; } } diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h index fa49c725f5..d7148ca57a 100644 --- a/src/quick/items/qquickflickable_p_p.h +++ b/src/quick/items/qquickflickable_p_p.h @@ -93,7 +93,7 @@ public: AxisData(QQuickFlickablePrivate *fp, void (QQuickFlickablePrivate::*func)(qreal)) : move(fp, func) , transitionToBounds(0) - , viewSize(-1), lastPos(0), velocity(0), startMargin(0), endMargin(0) + , viewSize(-1), lastPos(0), previousDragDelta(0), velocity(0), startMargin(0), endMargin(0) , origin(0) , transitionTo(0) , continuousFlickVelocity(0), velocityTime(), vTime(0) @@ -135,6 +135,7 @@ public: qreal dragStartOffset; qreal dragMinBound; qreal dragMaxBound; + qreal previousDragDelta; qreal velocity; qreal flickTarget; qreal startMargin; @@ -151,6 +152,7 @@ public: bool transitionToSet : 1; bool fixingUp : 1; bool inOvershoot : 1; + bool inRebound : 1; bool moving : 1; bool flicking : 1; bool dragging : 1; @@ -205,6 +207,7 @@ public: bool vMoved : 1; bool stealMouse : 1; bool pressed : 1; + bool scrollingPhase : 1; bool interactive : 1; bool calcVelocity : 1; bool pixelAligned : 1; @@ -214,6 +217,7 @@ public: qint64 lastPressTime; QPointF lastPos; QPointF pressPos; + QVector2D accumulatedWheelPixelDelta; qreal deceleration; qreal maxVelocity; qreal reportedVelocitySmoothing; @@ -244,7 +248,12 @@ public: void handleMouseMoveEvent(QMouseEvent *); void handleMouseReleaseEvent(QMouseEvent *); + void maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn); + void drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, + const QVector2D &deltas, bool overThreshold, bool momentum, const QVector2D &velocity); + qint64 computeCurrentTime(QInputEvent *event); + qreal devicePixelRatio(); // flickableData property static void data_append(QQmlListProperty<QObject> *, QObject *); diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index ed922eb67c..8ac6ac7d6d 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -1790,7 +1790,7 @@ bool QQuickWindowPrivate::deliverGestureEvent(QQuickItem *item, QNativeGestureEv void QQuickWindow::wheelEvent(QWheelEvent *event) { Q_D(QQuickWindow); - qCDebug(DBG_MOUSE) << "QQuickWindow::wheelEvent()" << event->pixelDelta() << event->angleDelta(); + qCDebug(DBG_MOUSE) << "QQuickWindow::wheelEvent()" << event->pixelDelta() << event->angleDelta() << event->phase(); //if the actual wheel event was accepted, accept the compatibility wheel event and return early if (d->lastWheelEventAccepted && event->angleDelta().isNull() && event->phase() == Qt::ScrollUpdate) |