diff options
author | Shawn Rutledge <[email protected]> | 2022-05-06 16:31:37 +0200 |
---|---|---|
committer | Shawn Rutledge <[email protected]> | 2022-05-11 12:56:22 +0200 |
commit | 8068bde891eefe99a43deb83e58166b476f65225 (patch) | |
tree | 477456cdccce7346cd6c13f2d551a5593f5b7a4f | |
parent | 5b0bb8d78e1854f3a23d7dd0bb286f524b272c37 (diff) |
QQuickFlickable: fix flicking when item is rotated
QEventPoint::velocity() is in screen coordinates, as documented; it needs
to be transformed to local coordinates in case the Flickable is rotated
and/or scaled. windowToItemTransform() gives us a composite transform
matrix; we remove the translation components to get only the rotation
and scaling.
Also fix the press position calculation in handleReleaseEvent().
With the manual test one can pinch to scale and rotate a Flickable, then
verify that with a single finger, the Flickable gets scrolled by an
appropriate distance in spite of that.
Done-with: Ivan Solovev
Fixes: QTBUG-99639
Pick-to: 6.3 6.2
Change-Id: I60af1dd932835d358baa1422523cc24b2ab046a0
Reviewed-by: Ivan Solovev <[email protected]>
Reviewed-by: Paul Olav Tvete <[email protected]>
-rw-r--r-- | src/quick/items/qquickflickable.cpp | 23 | ||||
-rw-r--r-- | src/quick/items/qquickflickable_p_p.h | 1 | ||||
-rw-r--r-- | tests/auto/quick/qquickflickable/data/rotatedFlickable.qml | 32 | ||||
-rw-r--r-- | tests/auto/quick/qquickflickable/tst_qquickflickable.cpp | 53 | ||||
-rw-r--r-- | tests/manual/pointer/pinchFlickable.qml | 84 |
5 files changed, 188 insertions, 5 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 7c42e9b1d4..8dc9a9afe6 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -1055,6 +1055,17 @@ void QQuickFlickable::setSynchronousDrag(bool v) } } +/*! \internal + Take the velocity of the first point from the given \a event and transform + it to the local coordinate system (taking scale and rotation into account). +*/ +QVector2D QQuickFlickablePrivate::firstPointLocalVelocity(QPointerEvent *event) +{ + QTransform transform = windowToItemTransform(); + // rotate and scale the velocity vector from scene to local + return QVector2D(transform.map(event->point(0).velocity().toPointF()) - transform.map(QPointF())); +} + qint64 QQuickFlickablePrivate::computeCurrentTime(QInputEvent *event) const { if (0 != event->timestamp()) @@ -1362,9 +1373,9 @@ void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) qint64 currentTimestamp = computeCurrentTime(event); const auto &firstPoint = event->points().first(); const auto &pos = firstPoint.position(); - QVector2D deltas = QVector2D(pos - q->mapFromGlobal(firstPoint.globalPressPosition())); + const QVector2D deltas = QVector2D(pos - q->mapFromGlobal(firstPoint.globalPressPosition())); + const QVector2D velocity = firstPointLocalVelocity(event); bool overThreshold = false; - QVector2D velocity = event->point(0).velocity(); if (q->yflick()) overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); @@ -1396,12 +1407,14 @@ void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) bool canBoost = false; const auto pos = event->points().first().position(); - const auto pressPos = event->points().first().pressPosition(); + const auto pressPos = q->mapFromGlobal(event->points().first().globalPressPosition()); + const QVector2D eventVelocity = firstPointLocalVelocity(event); + qCDebug(lcVel) << event->deviceType() << event->type() << "velocity" << event->points().first().velocity() << "transformed to local" << eventVelocity; qreal vVelocity = 0; if (elapsed < 100 && vData.velocity != 0.) { vVelocity = (event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity) - ? event->point(0).velocity().y() : vData.velocity); + ? eventVelocity.y() : vData.velocity); } if ((vData.atBeginning && vVelocity > 0.) || (vData.atEnd && vVelocity < 0.)) { vVelocity /= 2; @@ -1416,7 +1429,7 @@ void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) qreal hVelocity = 0; if (elapsed < 100 && hData.velocity != 0.) { hVelocity = (event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity) - ? event->point(0).velocity().x() : hData.velocity); + ? eventVelocity.x() : hData.velocity); } if ((hData.atBeginning && hVelocity > 0.) || (hData.atEnd && hVelocity < 0.)) { hVelocity /= 2; diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h index 388c70472f..9bc33d436f 100644 --- a/src/quick/items/qquickflickable_p_p.h +++ b/src/quick/items/qquickflickable_p_p.h @@ -268,6 +268,7 @@ public: const QVector2D &deltas, bool overThreshold, bool momentum, bool velocitySensitiveOverBounds, const QVector2D &velocity); + QVector2D firstPointLocalVelocity(QPointerEvent *event); qint64 computeCurrentTime(QInputEvent *event) const; qreal devicePixelRatio() const; diff --git a/tests/auto/quick/qquickflickable/data/rotatedFlickable.qml b/tests/auto/quick/qquickflickable/data/rotatedFlickable.qml new file mode 100644 index 0000000000..04816c178b --- /dev/null +++ b/tests/auto/quick/qquickflickable/data/rotatedFlickable.qml @@ -0,0 +1,32 @@ +import QtQuick 2.0 + +Item { + width: 300 + height: 300 + + Flickable { + id: flickable + anchors.fill: parent + contentHeight: column.height + flickDeceleration: 5000 // speed up the test run + + Column { + id: column + width: parent.width + + Repeater { + model: 255 + + delegate: Rectangle { + width: column.width + height: 50 + color: "gray" + Text { + anchors.centerIn: parent + text: index + } + } + } + } + } +} diff --git a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp index e13ce50e5e..067fdd5b77 100644 --- a/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp +++ b/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp @@ -236,6 +236,8 @@ private slots: void ignoreNonLeftMouseButtons(); void ignoreNonLeftMouseButtons_data(); void receiveTapOutsideContentItem(); + void flickWhenRotated_data(); + void flickWhenRotated(); private: void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to); @@ -2801,6 +2803,57 @@ void tst_qquickflickable::receiveTapOutsideContentItem() QCOMPARE(clickedSpy.count(), 2); } +void tst_qquickflickable::flickWhenRotated_data() +{ + QTest::addColumn<qreal>("rootRotation"); + QTest::addColumn<qreal>("flickableRotation"); + QTest::addColumn<qreal>("scale"); + + static constexpr qreal rotations[] = { 0, 30, 90, 180, 270 }; + static constexpr qreal scales[] = { 0.3, 1, 1.5 }; + + for (const auto pr : rotations) { + for (const auto fr : rotations) { + if (pr <= 90) { + for (const auto s : scales) + QTest::addRow("parent: %g, flickable: %g, scale: %g", pr, fr) << pr << fr << s; + } else { + // don't bother trying every scale with every set of rotations, to save time + QTest::addRow("parent: %g, flickable: %g, scale: 1", pr, fr) << pr << fr << qreal(1); + } + } + } +} + +void tst_qquickflickable::flickWhenRotated() // QTBUG-99639 +{ + QFETCH(qreal, rootRotation); + QFETCH(qreal, flickableRotation); + QFETCH(qreal, scale); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("rotatedFlickable.qml"))); + QQuickItem *rootItem = window.rootObject(); + QVERIFY(rootItem); + QQuickFlickable *flickable = rootItem->findChild<QQuickFlickable*>(); + QVERIFY(flickable); + + rootItem->setRotation(rootRotation); + flickable->setRotation(flickableRotation); + rootItem->setScale(scale); + QVERIFY(flickable->isAtYBeginning()); + + // Flick in Y direction in Flickable's coordinate system and check how much it moved + const QPointF startPoint = flickable->mapToGlobal(QPoint(20, 180)); + const QPointF endPoint = flickable->mapToGlobal(QPoint(20, 40)); + flick(&window, window.mapFromGlobal(startPoint).toPoint(), window.mapFromGlobal(endPoint).toPoint(), 100); + QTRY_VERIFY(flickable->isMoving()); + QTRY_VERIFY(!flickable->isMoving()); + qCDebug(lcTests) << "flicking from" << startPoint << "to" << endPoint << ": ended at contentY" << flickable->contentY(); + // usually flickable->contentY() is at 275 or very close + QVERIFY(!flickable->isAtYBeginning()); +} + QTEST_MAIN(tst_qquickflickable) #include "tst_qquickflickable.moc" diff --git a/tests/manual/pointer/pinchFlickable.qml b/tests/manual/pointer/pinchFlickable.qml new file mode 100644 index 0000000000..48a00b4b1b --- /dev/null +++ b/tests/manual/pointer/pinchFlickable.qml @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick + +Item { + width: 300 + height: 300 + + Flickable { + id: flickable + anchors.fill: parent + contentHeight: column.height + + Column { + id: column + width: parent.width + + Repeater { + model: 255 + + delegate: Rectangle { + width: column.width + height: 50 + color: flickable.dragging ? "steelblue" : "gray" + Text { + anchors.centerIn: parent + text: index + } + } + } + } + } + PinchHandler { + target: flickable + } +} |