aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquickflickable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquickflickable.cpp')
-rw-r--r--src/quick/items/qquickflickable.cpp329
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
{