diff options
Diffstat (limited to 'src/quick/items/qquickflickable.cpp')
-rw-r--r-- | src/quick/items/qquickflickable.cpp | 329 |
1 files changed, 214 insertions, 115 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index c2f71a9438..715f75cde7 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -7,7 +7,7 @@ #include "qquickwindow.h" #include "qquickwindow_p.h" #include "qquickmousearea_p.h" -#if QT_CONFIG(draganddrop) +#if QT_CONFIG(quick_draganddrop) #include "qquickdrag_p.h" #endif @@ -22,7 +22,7 @@ #include <QtGui/private/qeventpoint_p.h> #include <QtGui/qstylehints.h> #include <QtCore/qmath.h> -#include <qpa/qplatformintegration.h> +#include <qpa/qplatformtheme.h> #include <math.h> #include <cmath> @@ -40,21 +40,6 @@ Q_LOGGING_CATEGORY(lcVel, "qt.quick.flickable.velocity") // 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); } @@ -101,7 +86,7 @@ void QQuickFlickableVisibleArea::updateVisible() qreal pagePos = 0; qreal pageSize = 0; if (!qFuzzyIsNull(maxYBounds)) { - qreal y = p->pixelAligned ? Round(p->vData.move.value()) : p->vData.move.value(); + qreal y = p->pixelAligned ? std::round(p->vData.move.value()) : p->vData.move.value(); pagePos = (-y + flickable->minYExtent()) / maxYBounds; pageSize = viewheight / maxYBounds; } @@ -120,7 +105,7 @@ void QQuickFlickableVisibleArea::updateVisible() const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent(); const qreal maxXBounds = maxxextent + viewwidth; if (!qFuzzyIsNull(maxXBounds)) { - qreal x = p->pixelAligned ? Round(p->hData.move.value()) : p->hData.move.value(); + qreal x = p->pixelAligned ? std::round(p->hData.move.value()) : p->hData.move.value(); pagePos = (-x + flickable->minXExtent()) / maxXBounds; pageSize = viewwidth / maxXBounds; } else { @@ -249,8 +234,9 @@ QQuickFlickablePrivate::QQuickFlickablePrivate() , syncDrag(false) , lastPosTime(-1) , lastPressTime(0) - , deceleration(QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickDeceleration).toReal()) - , maxVelocity(QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickMaximumVelocity).toReal()) + , deceleration(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickDeceleration).toReal()) + , wheelDeceleration(15000) + , maxVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal()) , delayedPressEvent(nullptr), pressDelay(0), fixupDuration(400) , flickBoost(1.0), initialWheelFlickDistance(qApp->styleHints()->wheelScrollLines() * 24) , fixupMode(Normal), vTime(0), visibleArea(nullptr) @@ -259,6 +245,9 @@ QQuickFlickablePrivate::QQuickFlickablePrivate() , boundsMovement(QQuickFlickable::FollowBoundsBehavior) , rebound(nullptr) { + const int wheelDecelerationEnv = qEnvironmentVariableIntValue("QT_QUICK_FLICKABLE_WHEEL_DECELERATION"); + if (wheelDecelerationEnv > 0) + wheelDeceleration = wheelDecelerationEnv; } void QQuickFlickablePrivate::init() @@ -276,6 +265,7 @@ void QQuickFlickablePrivate::init() q->setFlag(QQuickItem::ItemIsViewport); QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem); viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry); + setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred); } /*! @@ -339,20 +329,21 @@ void QQuickFlickablePrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometr } } -bool QQuickFlickablePrivate::flickX(qreal velocity) +bool QQuickFlickablePrivate::flickX(QEvent::Type eventType, qreal velocity) { Q_Q(QQuickFlickable); - return flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity); + return flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, eventType, velocity); } -bool QQuickFlickablePrivate::flickY(qreal velocity) +bool QQuickFlickablePrivate::flickY(QEvent::Type eventType, qreal velocity) { Q_Q(QQuickFlickable); - return flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity); + return flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, eventType, velocity); } bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal, - QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) + QQuickTimeLineCallback::Callback fixupCallback, + QEvent::Type eventType, qreal velocity) { Q_Q(QQuickFlickable); qreal maxDistance = -1; @@ -374,13 +365,14 @@ bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExt v = maxVelocity; } + qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration; + qCDebug(lcFlickable) << "choosing deceleration" << accel << "for" << eventType; // 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)); + qreal target = std::round(data.move.value() - dist); dist = -target + data.move.value(); accel = v2 / (2.0f * qAbs(dist)); @@ -502,18 +494,18 @@ void QQuickFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExt } else if (data.move.value() <= maxExtent) { resetTimeline(data); adjustContentPos(data, maxExtent); - } else if (-Round(-data.move.value()) != data.move.value()) { + } else if (-std::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); + if (std::abs(std::round(val) - val) < 0.25) // round small differences + val = std::round(val); else if (data.smoothVelocity.value() > 0) // continue direction of motion for larger - val = -std::floor(-val); + val = std::ceil(val); else if (data.smoothVelocity.value() < 0) - val = -std::ceil(-val); + val = std::floor(val); else // otherwise round - val = -Round(-val); + val = std::round(val); timeline.set(data.move, val); } data.inOvershoot = false; @@ -549,7 +541,7 @@ void QQuickFlickablePrivate::updateBeginningEnd() // Vertical const qreal maxyextent = -q->maxYExtent(); const qreal minyextent = -q->minYExtent(); - const qreal ypos = -vData.move.value(); + const qreal ypos = pixelAligned ? -std::round(vData.move.value()) : -vData.move.value(); bool atBeginning = fuzzyLessThanOrEqualTo(ypos, std::ceil(minyextent)); bool atEnd = fuzzyLessThanOrEqualTo(std::floor(maxyextent), ypos); @@ -569,7 +561,7 @@ void QQuickFlickablePrivate::updateBeginningEnd() // Horizontal const qreal maxxextent = -q->maxXExtent(); const qreal minxextent = -q->minXExtent(); - const qreal xpos = -hData.move.value(); + const qreal xpos = pixelAligned ? -std::round(hData.move.value()) : -hData.move.value(); atBeginning = fuzzyLessThanOrEqualTo(xpos, std::ceil(minxextent)); atEnd = fuzzyLessThanOrEqualTo(std::floor(maxxextent), xpos); @@ -764,8 +756,6 @@ void QQuickFlickablePrivate::updateBeginningEnd() \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) @@ -1111,14 +1101,17 @@ void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event) } q->setKeepMouseGrab(stealMouse); - maybeBeginDrag(computeCurrentTime(event), event->points().first().position()); + maybeBeginDrag(computeCurrentTime(event), event->points().first().position(), + event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->buttons() + : Qt::NoButton); } -void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn) +void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn, Qt::MouseButtons buttons) { Q_Q(QQuickFlickable); clearDelayedPress(); - pressed = true; + // consider dragging only when event is left mouse button or touch event which has no button + pressed = buttons.testFlag(Qt::LeftButton) || (buttons == Qt::NoButton); if (hData.transitionToBounds) hData.transitionToBounds->stopTransition(); @@ -1382,10 +1375,14 @@ void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) const QVector2D velocity = firstPointLocalVelocity(event); bool overThreshold = false; - if (q->yflick()) - overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); - if (q->xflick()) - overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); + if (event->pointCount() == 1) { + if (q->yflick()) + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); + if (q->xflick()) + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); + } else { + qCDebug(lcFilter) << q->objectName() << "ignoring multi-touch" << event; + } drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity); } @@ -1447,24 +1444,32 @@ void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) } flickBoost = canBoost ? qBound(1.0, flickBoost+0.25, QML_FLICK_MULTIFLICK_MAXBOOST) : 1.0; - const int flickThreshold = QGuiApplicationPrivate::platformIntegration()->styleHint(QPlatformIntegration::FlickStartDistance).toInt(); + const int flickThreshold = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickStartDistance).toInt(); + + bool anyPointGrabbed = event->points().constEnd() != + std::find_if(event->points().constBegin(),event->points().constEnd(), + [q, event](const QEventPoint &point) { return event->exclusiveGrabber(point) == q; }); bool flickedVertically = false; vVelocity *= flickBoost; - bool isVerticalFlickAllowed = q->yflick() && qAbs(vVelocity) > MinimumFlickVelocity && qAbs(pos.y() - pressPos.y()) > flickThreshold; + const bool isVerticalFlickAllowed = anyPointGrabbed && + q->yflick() && qAbs(vVelocity) > _q_MinimumFlickVelocity && + qAbs(pos.y() - pressPos.y()) > flickThreshold; if (isVerticalFlickAllowed) { velocityTimeline.reset(vData.smoothVelocity); vData.smoothVelocity.setValue(-vVelocity); - flickedVertically = flickY(vVelocity); + flickedVertically = flickY(event->type(), vVelocity); } bool flickedHorizontally = false; hVelocity *= flickBoost; - bool isHorizontalFlickAllowed = q->xflick() && qAbs(hVelocity) > MinimumFlickVelocity && qAbs(pos.x() - pressPos.x()) > flickThreshold; + const bool isHorizontalFlickAllowed = anyPointGrabbed && + q->xflick() && qAbs(hVelocity) > _q_MinimumFlickVelocity && + qAbs(pos.x() - pressPos.x()) > flickThreshold; if (isHorizontalFlickAllowed) { velocityTimeline.reset(hData.smoothVelocity); hData.smoothVelocity.setValue(-hVelocity); - flickedHorizontally = flickX(hVelocity); + flickedHorizontally = flickX(event->type(), hVelocity); } if (!isVerticalFlickAllowed) @@ -1474,8 +1479,15 @@ void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) fixupX(); flickingStarted(flickedHorizontally, flickedVertically); - if (!isViewMoving()) + if (!isViewMoving()) { q->movementEnding(); + } else { + if (flickedVertically) + vMoved = true; + if (flickedHorizontally) + hMoved = true; + q->movementStarting(); + } } void QQuickFlickable::mousePressEvent(QMouseEvent *event) @@ -1508,13 +1520,14 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event) if (d->delayedPressEvent) { d->replayDelayedPress(); - // Now send the release - if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(event->point(0)))) { - // not copying or detaching anything, so make sure we return the original event unchanged - const auto oldPosition = event->point(0).position(); - QMutableEventPoint::setPosition(event->point(0), grabber->mapFromScene(event->scenePosition())); + auto &firstPoint = event->point(0); + if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) { + // Since we sent the delayed press to the window, we need to resend the release to the window too. + // We're not copying or detaching, so restore the original event position afterwards. + const auto oldPosition = firstPoint.position(); + QMutableEventPoint::setPosition(firstPoint, event->scenePosition()); QCoreApplication::sendEvent(window(), event); - QMutableEventPoint::setPosition(event->point(0), oldPosition); + QMutableEventPoint::setPosition(firstPoint, oldPosition); } // And the event has been consumed @@ -1533,6 +1546,15 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event) void QQuickFlickable::touchEvent(QTouchEvent *event) { Q_D(QQuickFlickable); + + if (event->type() == QEvent::TouchCancel) { + if (d->interactive && d->wantsPointerEvent(event)) + d->cancelInteraction(); + else + QQuickItem::touchEvent(event); + return; + } + bool unhandled = false; const auto &firstPoint = event->points().first(); switch (firstPoint.state()) { @@ -1558,11 +1580,11 @@ void QQuickFlickable::touchEvent(QTouchEvent *event) if (d->delayedPressEvent) { d->replayDelayedPress(); - // Now send the release - auto &firstPoint = event->point(0); - if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(firstPoint))) { - const auto localPos = grabber->mapFromScene(firstPoint.scenePosition()); - QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos)); + const auto &firstPoint = event->point(0); + if (const auto *grabber = event->exclusiveGrabber(firstPoint); grabber && grabber->isQuickItemType()) { + // Since we sent the delayed press to the window, we need to resend the release to the window too. + QScopedPointer<QPointerEvent> localizedEvent( + QQuickDeliveryAgentPrivate::clonePointerEvent(event, firstPoint.scenePosition())); QCoreApplication::sendEvent(window(), localizedEvent.data()); } @@ -1644,48 +1666,117 @@ void QQuickFlickable::wheelEvent(QWheelEvent *event) // no pixel delta (physical mouse wheel, or "dumb" touchpad), so use angleDelta int xDelta = event->angleDelta().x(); int yDelta = event->angleDelta().y(); - // For a single "clicky" wheel event (angleDelta +/- 120), - // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines(). - // The decel algo from there is - // qreal dist = v2 / (accel * 2.0); - // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2) - // now solve for dt: - // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance) - if (!isMoving()) - elapsed = 120 / qSqrt(d->deceleration * 2 * d->initialWheelFlickDistance); - if (yflick() && yDelta != 0) { - qreal instVelocity = yDelta / elapsed; - // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction - if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0)) - d->vData.velocityBuffer.clear(); - d->vData.addVelocitySample(instVelocity, d->maxVelocity); - d->vData.updateVelocity(); - if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) { - const bool newFlick = d->flickY(d->vData.velocity); - if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) { - d->flickingStarted(false, true); - d->vMoved = true; + + if (d->wheelDeceleration > _q_MaximumWheelDeceleration) { + const qreal wheelScroll = -qApp->styleHints()->wheelScrollLines() * 24; + // If wheelDeceleration is very large, i.e. the user or the platform does not want to have any mouse wheel + // acceleration behavior, we want to move a distance proportional to QStyleHints::wheelScrollLines() + if (yflick() && yDelta != 0) { + d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. + d->vMoved = true; + qreal scrollPixel = (-yDelta / 120.0 * wheelScroll); + if (scrollPixel > 0) { // Forward direction (away from user) + if (d->vData.move.value() >= minYExtent()) + d->vMoved = false; + } else { // Backward direction (towards user) + if (d->vData.move.value() <= maxYExtent()) + d->vMoved = false; + } + if (d->vMoved) { + if (d->boundsBehavior == QQuickFlickable::StopAtBounds) { + const qreal estContentPos = scrollPixel + d->vData.move.value(); + if (scrollPixel > 0) { // Forward direction (away from user) + if (estContentPos > minYExtent()) + scrollPixel = minYExtent() - d->vData.move.value(); + } else { // Backward direction (towards user) + if (estContentPos < maxYExtent()) + scrollPixel = maxYExtent() - d->vData.move.value(); + } + } + d->resetTimeline(d->vData); movementStarting(); + d->timeline.moveBy(d->vData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4); + d->vData.fixingUp = true; + d->timeline.callback(QQuickTimeLineCallback(&d->vData.move, QQuickFlickablePrivate::fixupY_callback, d)); } event->accept(); } - } - if (xflick() && xDelta != 0) { - qreal instVelocity = xDelta / elapsed; - // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction - if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0)) - d->hData.velocityBuffer.clear(); - d->hData.addVelocitySample(instVelocity, d->maxVelocity); - d->hData.updateVelocity(); - if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) { - const bool newFlick = d->flickX(d->hData.velocity); - if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) { - d->flickingStarted(true, false); - d->hMoved = true; + if (xflick() && xDelta != 0) { + d->moveReason = QQuickFlickablePrivate::Mouse; // ItemViews will set fixupMode to Immediate in fixup() without this. + d->hMoved = true; + qreal scrollPixel = (-xDelta / 120.0 * wheelScroll); + if (scrollPixel > 0) { // Forward direction (away from user) + if (d->hData.move.value() >= minXExtent()) + d->hMoved = false; + } else { // Backward direction (towards user) + if (d->hData.move.value() <= maxXExtent()) + d->hMoved = false; + } + if (d->hMoved) { + if (d->boundsBehavior == QQuickFlickable::StopAtBounds) { + const qreal estContentPos = scrollPixel + d->hData.move.value(); + if (scrollPixel > 0) { // Forward direction (away from user) + if (estContentPos > minXExtent()) + scrollPixel = minXExtent() - d->hData.move.value(); + } else { // Backward direction (towards user) + if (estContentPos < maxXExtent()) + scrollPixel = maxXExtent() - d->hData.move.value(); + } + } + d->resetTimeline(d->hData); movementStarting(); + d->timeline.moveBy(d->hData.move, scrollPixel, QEasingCurve(QEasingCurve::OutExpo), 3*d->fixupDuration/4); + d->hData.fixingUp = true; + d->timeline.callback(QQuickTimeLineCallback(&d->hData.move, QQuickFlickablePrivate::fixupX_callback, d)); } event->accept(); } + } else { + // wheelDeceleration is set to some reasonable value: the user or the platform wants to have + // the classic Qt Quick mouse wheel acceleration behavior. + // For a single "clicky" wheel event (angleDelta +/- 120), + // we want flick() to end up moving a distance proportional to QStyleHints::wheelScrollLines(). + // The decel algo from there is + // qreal dist = v2 / (accel * 2.0); + // i.e. initialWheelFlickDistance = (120 / dt)^2 / (deceleration * 2) + // now solve for dt: + // dt = 120 / sqrt(deceleration * 2 * initialWheelFlickDistance) + if (!isMoving()) + elapsed = 120 / qSqrt(d->wheelDeceleration * 2 * d->initialWheelFlickDistance); + if (yflick() && yDelta != 0) { + qreal instVelocity = yDelta / elapsed; + // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction + if ((instVelocity < 0 && d->vData.velocity > 0) || (instVelocity > 0 && d->vData.velocity < 0)) + d->vData.velocityBuffer.clear(); + d->vData.addVelocitySample(instVelocity, d->maxVelocity); + d->vData.updateVelocity(); + if ((yDelta > 0 && contentY() > -minYExtent()) || (yDelta < 0 && contentY() < -maxYExtent())) { + const bool newFlick = d->flickY(event->type(), d->vData.velocity); + if (newFlick && (d->vData.atBeginning != (yDelta > 0) || d->vData.atEnd != (yDelta < 0))) { + d->flickingStarted(false, true); + d->vMoved = true; + movementStarting(); + } + event->accept(); + } + } + if (xflick() && xDelta != 0) { + qreal instVelocity = xDelta / elapsed; + // if the direction has changed, start over with filtering, to allow instant movement in the opposite direction + if ((instVelocity < 0 && d->hData.velocity > 0) || (instVelocity > 0 && d->hData.velocity < 0)) + d->hData.velocityBuffer.clear(); + d->hData.addVelocitySample(instVelocity, d->maxVelocity); + d->hData.updateVelocity(); + if ((xDelta > 0 && contentX() > -minXExtent()) || (xDelta < 0 && contentX() < -maxXExtent())) { + const bool newFlick = d->flickX(event->type(), d->hData.velocity); + if (newFlick && (d->hData.atBeginning != (xDelta > 0) || d->hData.atEnd != (xDelta < 0))) { + d->flickingStarted(true, false); + d->hMoved = true; + movementStarting(); + } + event->accept(); + } + } } } else { // use pixelDelta (probably from a trackpad): this is where we want to be on most platforms eventually @@ -1812,7 +1903,7 @@ void QQuickFlickablePrivate::replayDelayedPress() void QQuickFlickablePrivate::setViewportX(qreal x) { Q_Q(QQuickFlickable); - qreal effectiveX = pixelAligned ? -Round(-x) : x; + qreal effectiveX = pixelAligned ? -std::round(-x) : x; const qreal maxX = q->maxXExtent(); const qreal minX = q->minXExtent(); @@ -1847,7 +1938,7 @@ void QQuickFlickablePrivate::setViewportX(qreal x) void QQuickFlickablePrivate::setViewportY(qreal y) { Q_Q(QQuickFlickable); - qreal effectiveY = pixelAligned ? -Round(-y) : y; + qreal effectiveY = pixelAligned ? -std::round(-y) : y; const qreal maxY = q->maxYExtent(); const qreal minY = q->minYExtent(); @@ -2015,7 +2106,7 @@ void QQuickFlickable::geometryChange(const QRectF &newGeometry, const QRectF &ol 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. + just like a real touchscreen flick. */ void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity) @@ -2027,8 +2118,8 @@ void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity) 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); + const bool flickedX = xflick() && !qFuzzyIsNull(xVelocity) && d->flickX(QEvent::TouchUpdate, xVelocity); + const bool flickedY = yflick() && !qFuzzyIsNull(yVelocity) && d->flickY(QEvent::TouchUpdate, yVelocity); if (flickedX) d->hMoved = true; @@ -2580,12 +2671,31 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev QQuickDeliveryAgentPrivate::isTabletEvent(event))) return false; // don't filter hover events or wheel events, for example Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself"); - qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; Q_D(QQuickFlickable); // If a touch event contains a new press point, don't steal right away: watch the movements for a while if (isTouch && static_cast<QTouchEvent *>(event)->touchPointStates().testFlag(QEventPoint::State::Pressed)) d->stealMouse = false; + // If multiple touchpoints are within bounds, don't grab: it's probably meant for multi-touch interaction in some child + if (event->pointCount() > 1) { + qCDebug(lcFilter) << objectName() << "ignoring multi-touch" << event << "for" << receiver; + d->stealMouse = false; + } else { + qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; + } + const auto &firstPoint = event->points().first(); + + if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) { + // We have an exclusive grab (since we're e.g dragging), but at the same time, we have + // a child with a passive grab (which is why this filter is being called). And because + // of that, we end up getting the same pointer events twice; First in our own event + // handlers (because of the grab), then once more in here, since we filter the child. + // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more + // from below), we mark the event as filtered, and simply return. + event->setAccepted(true); + return true; + } + QPointF localPos = mapFromScene(firstPoint.scenePosition()); bool receiverDisabled = receiver && !receiver->isEnabled(); bool stealThisEvent = d->stealMouse; @@ -2595,7 +2705,7 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev // Special case for MouseArea, try to guess what it does with the event if (auto *mouseArea = qmlobject_cast<QQuickMouseArea *>(receiver)) { bool preventStealing = mouseArea->preventStealing(); -#if QT_CONFIG(draganddrop) +#if QT_CONFIG(quick_draganddrop) if (mouseArea->drag() && mouseArea->drag()->target()) preventStealing = true; #endif @@ -2720,21 +2830,10 @@ void QQuickFlickable::setMaximumFlickVelocity(qreal v) \qmlproperty real QtQuick::Flickable::flickDeceleration This property holds the rate at which a flick will decelerate: the higher the number, the faster it slows down when the user stops - flicking via touch, touchpad or mouse wheel. For example 0.0001 is nearly + flicking via touch. For example 0.0001 is nearly "frictionless", and 10000 feels quite "sticky". The default value is platform dependent. Values of zero or less are not allowed. - - \note For touchpad flicking, some platforms drive Flickable directly by - sending QWheelEvents with QWheelEvent::phase() being \c Qt::ScrollMomentum, - after the user has released all fingers from the touchpad. In that case, - the operating system is controlling the deceleration, and this property has - no effect. - - \note For mouse wheel scrolling, and for gesture scrolling on touchpads - that do not have a momentum phase, extremely large values of - flickDeceleration can make Flickable very resistant to scrolling, - especially if \l maximumFlickVelocity is too small. */ qreal QQuickFlickable::flickDeceleration() const { |