diff options
Diffstat (limited to 'tests/auto/quick/pointerhandlers/qquickpinchhandler')
8 files changed, 677 insertions, 316 deletions
diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/BLACKLIST b/tests/auto/quick/pointerhandlers/qquickpinchhandler/BLACKLIST new file mode 100644 index 0000000000..701cdae344 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/BLACKLIST @@ -0,0 +1,3 @@ +# QTBUG-103066 +[scaleNativeGesture] +android diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/CMakeLists.txt b/tests/auto/quick/pointerhandlers/qquickpinchhandler/CMakeLists.txt index 09550b143d..1334607ab2 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/CMakeLists.txt +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/CMakeLists.txt @@ -1,9 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qquickpinchhandler.pro. ##################################################################### ## tst_qquickpinchhandler Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qquickpinchhandler LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -12,21 +21,14 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qquickpinchhandler SOURCES - ../../../shared/util.cpp ../../../shared/util.h - ../../shared/geometrytestutil.cpp ../../shared/geometrytestutil.h - ../../shared/viewtestutil.cpp ../../shared/viewtestutil.h - ../../shared/visualtestutil.cpp ../../shared/visualtestutil.h tst_qquickpinchhandler.cpp - DEFINES - QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\" - INCLUDE_DIRECTORIES - ../../../shared - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate Qt::QmlPrivate Qt::QuickPrivate + Qt::QuickTestUtilsPrivate TESTDATA ${test_data} ) @@ -38,10 +40,10 @@ qt_internal_add_test(tst_qquickpinchhandler qt_internal_extend_target(tst_qquickpinchhandler CONDITION ANDROID OR IOS DEFINES - QT_QMLTEST_DATADIR=\\\":/data\\\" + QT_QMLTEST_DATADIR=":/data" ) qt_internal_extend_target(tst_qquickpinchhandler CONDITION NOT ANDROID AND NOT IOS DEFINES - QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\" + QT_QMLTEST_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/nullTarget.qml b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/nullTarget.qml new file mode 100644 index 0000000000..9d9903fc0e --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/nullTarget.qml @@ -0,0 +1,33 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick 2.15 + +Item { + width: 320; height: 320 + property alias pinchScale: pinch.scale + + Rectangle { + objectName: "blackrect" + width: 200; height: 200 + color: "black" + antialiasing: true + scale: pinch.scale + rotation: pinch.rotation + x: pinch.translation.x + y: pinch.translation.y + + PinchHandler { + id: pinch + target: null + minimumScale: 0.5 + maximumScale: 4 + } + + Text { + color: "cyan" + anchors.centerIn: parent + text: "scale " + pinch.scale.toFixed(2) + " activeScale " + pinch.activeScale.toFixed(2) + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchAndDrag.qml b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchAndDrag.qml new file mode 100644 index 0000000000..ddd63ec720 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchAndDrag.qml @@ -0,0 +1,37 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick + +Rectangle { + id: whiteRect + property real scale: -1.0 + property int activeCount : 0 + property int deactiveCount : 0 + width: 320; height: 320 + color: "white" + Rectangle { + id: blackRect + objectName: "blackrect" + color: "black" + y: 50 + x: 50 + width: 100 + height: 100 + Text { color: "white"; text: "opacity: " + blackRect.opacity + "\nscale: " + blackRect.scale} + Rectangle { + color: "red" + width: 6; height: 6; radius: 3 + visible: pincharea.active + x: pincharea.centroid.position.x - radius + y: pincharea.centroid.position.y - radius + } + + DragHandler { } + + PinchHandler { + id: pincharea + objectName: "pinchHandler" + } + } + } diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml index 3cabde5f59..2b9b3eb156 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/pinchproperties.qml @@ -1,40 +1,49 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.12 Rectangle { id: whiteRect - property real scale: -1.0 + property real pinchScale: -1.0 property int activeCount : 0 property int deactiveCount : 0 width: 320; height: 320 color: "white" + + PointHandler { + id: ph1 + acceptedDevices: PointerDevice.TouchScreen + target: Rectangle { + parent: whiteRect + color: "cyan" + x: ph1.point.position.x - 3 + y: ph1.point.position.y - 3 + width: 6; height: 6; radius: 3 + } + } + + PointHandler { + id: ph2 + acceptedDevices: PointerDevice.TouchScreen + target: Rectangle { + parent: whiteRect + color: "lightgreen" + x: ph2.point.position.x - 3 + y: ph2.point.position.y - 3 + width: 6; height: 6; radius: 3 + } + } + + Text { + color: "magenta" + z: 1 + text: "scale: " + blackRect.scale + + "\npos: " + blackRect.x.toFixed(2) + ", " + blackRect.y.toFixed(2) + + "\ntranslation: active " + pincharea.activeTranslation.x.toFixed(2) + ", " + pincharea.activeTranslation.y.toFixed(2) + + "\n persistent " + pincharea.persistentTranslation.x.toFixed(2) + ", " + pincharea.persistentTranslation.y.toFixed(2) + } + Rectangle { id: blackRect objectName: "blackrect" @@ -43,8 +52,7 @@ Rectangle { x: 50 width: 100 height: 100 - opacity: (whiteRect.width-blackRect.x+whiteRect.height-blackRect.y-199)/200 - Text { color: "white"; text: "opacity: " + blackRect.opacity + "\nscale: " + blackRect.scale} + Rectangle { color: "red" width: 6; height: 6; radius: 3 @@ -56,22 +64,30 @@ Rectangle { PinchHandler { id: pincharea objectName: "pinchHandler" - minimumScale: 1.0 + minimumScale: 0.5 maximumScale: 4.0 minimumRotation: 0.0 maximumRotation: 90.0 xAxis.maximum: 140 yAxis.maximum: 170 onActiveChanged: { - whiteRect.scale = pincharea.scale + whiteRect.pinchScale = pincharea.scale if (active) ++activeCount else ++deactiveCount; } onUpdated: { - whiteRect.scale = pincharea.scale + whiteRect.pinchScale = pincharea.scale //whiteRect.pointCount = pincharea.pointCount } - } - } - } + } + } + + Rectangle { + color: "transparent"; border.color: "green" + width: 12; height: 12; radius: 6; border.width: 2 + visible: pincharea.active + x: pincharea.centroid.scenePosition.x - radius + y: pincharea.centroid.scenePosition.y - radius + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/threeFingers.qml b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/threeFingers.qml index 4d1a520c01..ed9220f99e 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/threeFingers.qml +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/threeFingers.qml @@ -1,39 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -import QtQuick 2.12 +import QtQuick Rectangle { id: root - property variant centroid : pinchHandler.centroid - property real scale: pinchHandler.scale - property int pointCount: 0 - property bool pinchActive: pinchHandler.active width: 240; height: 320 Rectangle { @@ -48,6 +19,7 @@ Rectangle { PinchHandler { id: pinchHandler objectName: "pinchHandler" + dragThreshold: 10 minimumScale: 0.5 maximumScale: 2.0 minimumRotation: 0.0 diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/transformedPinchHandler.qml b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/transformedPinchHandler.qml index 46e9ccca87..f25d0e9f38 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/transformedPinchHandler.qml +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/data/transformedPinchHandler.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.12 diff --git a/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp b/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp index cd24d8643e..78483c5bdb 100644 --- a/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickpinchhandler/tst_qquickpinchhandler.cpp @@ -1,55 +1,41 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QtTest/QSignalSpy> #include <QtGui/QStyleHints> -#include <QtGui/private/qevent_p.h> +#include <QtGui/private/qeventpoint_p.h> #include <qpa/qwindowsysteminterface.h> #include <QtQuick/private/qquickpinchhandler_p.h> #include <QtQuick/private/qquickrectangle_p.h> #include <QtQuick/qquickview.h> #include <QtQml/qqmlcontext.h> -#include "../../../shared/util.h" -#include "../../shared/viewtestutil.h" +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") +class PinchHandler : public QQuickPinchHandler { +public: + const QQuickHandlerPoint &firstPoint() { return currentPoints().first(); } + const QQuickHandlerPoint &lastPoint() { return currentPoints().last(); } +}; + class tst_QQuickPinchHandler: public QQmlDataTest { Q_OBJECT public: - tst_QQuickPinchHandler() { } + tst_QQuickPinchHandler() : QQmlDataTest(QT_QMLTEST_DATADIR) { } private slots: void cleanupTestCase(); void pinchProperties(); + void scale_data(); void scale(); void scaleThreeFingers(); + void scaleNativeGesture_data(); + void scaleNativeGesture(); + void cumulativeNativeGestures_data(); + void cumulativeNativeGestures(); void pan(); void dragAxesEnabled_data(); void dragAxesEnabled(); @@ -59,8 +45,8 @@ private slots: void transformedpinchHandler(); private: - QQuickView *createView(); - QPointingDevice *device = QTest::createTouchDevice(); + QScopedPointer<QPointingDevice> touchscreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); + QScopedPointer<QPointingDevice> touchpad = QScopedPointer<QPointingDevice>(QTest::createTouchDevice(QInputDevice::DeviceType::TouchPad)); }; void tst_QQuickPinchHandler::cleanupTestCase() @@ -75,7 +61,7 @@ static bool withinBounds(qreal lower, qreal num, qreal upper) void tst_QQuickPinchHandler::pinchProperties() { - QScopedPointer<QQuickView> window(createView()); + QScopedPointer<QQuickView> window(QQuickViewTestUtils::createView()); window->setSource(testFileUrl("pinchproperties.qml")); window->show(); QVERIFY(window->rootObject() != nullptr); @@ -91,193 +77,292 @@ void tst_QQuickPinchHandler::pinchProperties() QVERIFY(rootItem != nullptr); QSignalSpy targetSpy(pinchHandler, SIGNAL(targetChanged())); pinchHandler->setTarget(rootItem); - QCOMPARE(targetSpy.count(),1); + QCOMPARE(targetSpy.size(),1); pinchHandler->setTarget(rootItem); - QCOMPARE(targetSpy.count(),1); - - // axis - /* - QCOMPARE(pinchHandler->axis(), QQuickPinch::XAndYAxis); - QSignalSpy axisSpy(pinchHandler, SIGNAL(dragAxisChanged())); - pinchHandler->setAxis(QQuickPinch::XAxis); - QCOMPARE(pinchHandler->axis(), QQuickPinch::XAxis); - QCOMPARE(axisSpy.count(),1); - pinchHandler->setAxis(QQuickPinch::XAxis); - QCOMPARE(axisSpy.count(),1); + QCOMPARE(targetSpy.size(),1); + + // drag axes + QCOMPARE(pinchHandler->xAxis()->enabled(), true); + QCOMPARE(pinchHandler->yAxis()->enabled(), true); + QSignalSpy xEnabledSpy(pinchHandler->xAxis(), &QQuickDragAxis::enabledChanged); + QSignalSpy yEnabledSpy(pinchHandler->yAxis(), &QQuickDragAxis::enabledChanged); + QSignalSpy scaleEnabledSpy(pinchHandler->scaleAxis(), &QQuickDragAxis::enabledChanged); + QSignalSpy rotationEnabledSpy(pinchHandler->rotationAxis(), &QQuickDragAxis::enabledChanged); + pinchHandler->xAxis()->setEnabled(false); + QCOMPARE(xEnabledSpy.count(), 1); + pinchHandler->yAxis()->setEnabled(false); + QCOMPARE(yEnabledSpy.count(), 1); + pinchHandler->scaleAxis()->setEnabled(false); + QCOMPARE(scaleEnabledSpy.count(), 1); + pinchHandler->rotationAxis()->setEnabled(false); + QCOMPARE(rotationEnabledSpy.count(), 1); // minimum and maximum drag properties - QSignalSpy xminSpy(pinchHandler, SIGNAL(minimumXChanged())); - QSignalSpy xmaxSpy(pinchHandler, SIGNAL(maximumXChanged())); - QSignalSpy yminSpy(pinchHandler, SIGNAL(minimumYChanged())); - QSignalSpy ymaxSpy(pinchHandler, SIGNAL(maximumYChanged())); - - QCOMPARE(pinchHandler->xmin(), 0.0); - QCOMPARE(pinchHandler->xmax(), rootItem->width()-blackRect->width()); - QCOMPARE(pinchHandler->ymin(), 0.0); - QCOMPARE(pinchHandler->ymax(), rootItem->height()-blackRect->height()); - - pinchHandler->setXmin(10); - pinchHandler->setXmax(10); - pinchHandler->setYmin(10); - pinchHandler->setYmax(10); - - QCOMPARE(pinchHandler->xmin(), 10.0); - QCOMPARE(pinchHandler->xmax(), 10.0); - QCOMPARE(pinchHandler->ymin(), 10.0); - QCOMPARE(pinchHandler->ymax(), 10.0); + QSignalSpy xminSpy(pinchHandler->xAxis(), &QQuickDragAxis::minimumChanged); + QSignalSpy xmaxSpy(pinchHandler->xAxis(), &QQuickDragAxis::maximumChanged); + QSignalSpy yminSpy(pinchHandler->yAxis(), &QQuickDragAxis::minimumChanged); + QSignalSpy ymaxSpy(pinchHandler->yAxis(), &QQuickDragAxis::maximumChanged); + + QCOMPARE(pinchHandler->xAxis()->minimum(), std::numeric_limits<qreal>::lowest()); + QCOMPARE(pinchHandler->xAxis()->maximum(), 140); + QCOMPARE(pinchHandler->yAxis()->minimum(), std::numeric_limits<qreal>::lowest()); + QCOMPARE(pinchHandler->yAxis()->maximum(), 170); + + pinchHandler->xAxis()->setMinimum(10); + pinchHandler->xAxis()->setMaximum(10); + pinchHandler->yAxis()->setMinimum(10); + pinchHandler->yAxis()->setMaximum(10); + + QCOMPARE(pinchHandler->xAxis()->minimum(), 10); + QCOMPARE(pinchHandler->xAxis()->maximum(), 10); + QCOMPARE(pinchHandler->yAxis()->minimum(), 10); + QCOMPARE(pinchHandler->yAxis()->maximum(), 10); QCOMPARE(xminSpy.count(),1); QCOMPARE(xmaxSpy.count(),1); QCOMPARE(yminSpy.count(),1); QCOMPARE(ymaxSpy.count(),1); - pinchHandler->setXmin(10); - pinchHandler->setXmax(10); - pinchHandler->setYmin(10); - pinchHandler->setYmax(10); + pinchHandler->xAxis()->setMinimum(10); + pinchHandler->xAxis()->setMaximum(10); + pinchHandler->yAxis()->setMinimum(10); + pinchHandler->yAxis()->setMaximum(10); QCOMPARE(xminSpy.count(),1); QCOMPARE(xmaxSpy.count(),1); QCOMPARE(yminSpy.count(),1); QCOMPARE(ymaxSpy.count(),1); - */ // minimum and maximum scale properties - QSignalSpy scaleMinSpy(pinchHandler, SIGNAL(minimumScaleChanged())); - QSignalSpy scaleMaxSpy(pinchHandler, SIGNAL(maximumScaleChanged())); - - QCOMPARE(pinchHandler->minimumScale(), 1.0); - QCOMPARE(pinchHandler->maximumScale(), 4.0); + QSignalSpy scaleAxisMinSpy(pinchHandler->scaleAxis(), &QQuickDragAxis::minimumChanged); + QSignalSpy scaleAxisMaxSpy(pinchHandler->scaleAxis(), &QQuickDragAxis::maximumChanged); - pinchHandler->setMinimumScale(0.5); - pinchHandler->setMaximumScale(1.5); + QCOMPARE(pinchHandler->scaleAxis()->minimum(), 0.5); + QCOMPARE(pinchHandler->scaleAxis()->maximum(), 4); +#if QT_DEPRECATED_SINCE(6, 5) +QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED QCOMPARE(pinchHandler->minimumScale(), 0.5); - QCOMPARE(pinchHandler->maximumScale(), 1.5); - - QCOMPARE(scaleMinSpy.count(),1); - QCOMPARE(scaleMaxSpy.count(),1); + QCOMPARE(pinchHandler->maximumScale(), 4); +QT_WARNING_POP +#endif - pinchHandler->setMinimumScale(0.5); - pinchHandler->setMaximumScale(1.5); + pinchHandler->scaleAxis()->setMinimum(0.25); + pinchHandler->scaleAxis()->setMaximum(1.5); - QCOMPARE(scaleMinSpy.count(),1); - QCOMPARE(scaleMaxSpy.count(),1); + QCOMPARE(pinchHandler->scaleAxis()->minimum(), 0.25); + QCOMPARE(pinchHandler->scaleAxis()->maximum(), 1.5); - // minimum and maximum rotation properties - QSignalSpy rotMinSpy(pinchHandler, SIGNAL(minimumRotationChanged())); - QSignalSpy rotMaxSpy(pinchHandler, SIGNAL(maximumRotationChanged())); - - QCOMPARE(pinchHandler->minimumRotation(), 0.0); - QCOMPARE(pinchHandler->maximumRotation(), 90.0); - - pinchHandler->setMinimumRotation(-90.0); - pinchHandler->setMaximumRotation(45.0); +#if QT_DEPRECATED_SINCE(6, 5) +QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + QCOMPARE(pinchHandler->minimumScale(), 0.25); + QCOMPARE(pinchHandler->maximumScale(), 1.5); +QT_WARNING_POP +#endif - QCOMPARE(pinchHandler->minimumRotation(), -90.0); - QCOMPARE(pinchHandler->maximumRotation(), 45.0); + QCOMPARE(scaleAxisMinSpy.size(),1); + QCOMPARE(scaleAxisMaxSpy.size(),1); - QCOMPARE(rotMinSpy.count(),1); - QCOMPARE(rotMaxSpy.count(),1); + pinchHandler->scaleAxis()->setMinimum(0.25); + pinchHandler->scaleAxis()->setMaximum(1.5); - pinchHandler->setMinimumRotation(-90.0); - pinchHandler->setMaximumRotation(45.0); + QCOMPARE(scaleAxisMinSpy.size(),1); + QCOMPARE(scaleAxisMaxSpy.size(),1); - QCOMPARE(rotMinSpy.count(),1); - QCOMPARE(rotMaxSpy.count(),1); + // minimum and maximum rotation properties + QSignalSpy rotAxisMinSpy(pinchHandler->rotationAxis(), &QQuickDragAxis::minimumChanged); + QSignalSpy rotAxisMaxSpy(pinchHandler->rotationAxis(), &QQuickDragAxis::maximumChanged); + +#if QT_DEPRECATED_SINCE(6, 5) +QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + QCOMPARE(pinchHandler->minimumRotation(), 0); + QCOMPARE(pinchHandler->maximumRotation(), 90); +QT_WARNING_POP +#endif + QCOMPARE(pinchHandler->rotationAxis()->minimum(), 0); + QCOMPARE(pinchHandler->rotationAxis()->maximum(), 90); + + pinchHandler->rotationAxis()->setMinimum(-90); + pinchHandler->rotationAxis()->setMaximum(45); + +#if QT_DEPRECATED_SINCE(6, 5) +QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + QCOMPARE(pinchHandler->minimumRotation(), -90); + QCOMPARE(pinchHandler->maximumRotation(), 45); +QT_WARNING_POP +#endif + + QCOMPARE(rotAxisMinSpy.size(),1); + QCOMPARE(rotAxisMaxSpy.size(),1); + + pinchHandler->rotationAxis()->setMinimum(-90); + pinchHandler->rotationAxis()->setMaximum(45); + + QCOMPARE(rotAxisMinSpy.size(),1); + QCOMPARE(rotAxisMaxSpy.size(),1); } -QMutableEventPoint makeTouchPoint(int id, QPoint p, QQuickView *v, QQuickItem *i) +QEventPoint makeTouchPoint(int id, QPoint p, QQuickView *v, QQuickItem *i) { - QMutableEventPoint touchPoint(id); - touchPoint.setPosition(i->mapFromScene(p)); - touchPoint.setGlobalPosition(v->mapToGlobal(p)); - touchPoint.setScenePosition(p); + QEventPoint touchPoint(id); + QMutableEventPoint::setPosition(touchPoint, i->mapFromScene(p)); + QMutableEventPoint::setGlobalPosition(touchPoint, v->mapToGlobal(p)); + QMutableEventPoint::setScenePosition(touchPoint, p); return touchPoint; } -void tst_QQuickPinchHandler::scale() +void tst_QQuickPinchHandler::scale_data() { - QQuickView *window = createView(); - QScopedPointer<QQuickView> scope(window); - window->setSource(testFileUrl("pinchproperties.qml")); - window->show(); - QVERIFY(QTest::qWaitForWindowExposed(window)); - QVERIFY(window->rootObject() != nullptr); - qApp->processEvents(); + QTest::addColumn<QUrl>("qmlfile"); + QTest::addColumn<bool>("hasTarget"); + QTest::newRow("targetModifying") << testFileUrl("pinchproperties.qml") << true; + QTest::newRow("nullTarget") << testFileUrl("nullTarget.qml") << false; +} - QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>("pinchHandler"); - QVERIFY(pinchHandler != nullptr); +void tst_QQuickPinchHandler::scale() +{ + QFETCH(QUrl, qmlfile); + QFETCH(bool, hasTarget); - QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject()); + QQuickView window; + QVERIFY(QQuickTest::showView(window, qmlfile)); + QQuickItem *root = qobject_cast<QQuickItem*>(window.rootObject()); QVERIFY(root != nullptr); - - // target - QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect"); + auto *pinchHandler = static_cast<PinchHandler *>(root->findChild<QQuickPinchHandler*>()); + QVERIFY(pinchHandler != nullptr); + QQuickItem *blackRect = (hasTarget ? pinchHandler->target() : pinchHandler->parentItem()); QVERIFY(blackRect != nullptr); + QSignalSpy grabChangedSpy(pinchHandler, SIGNAL(grabChanged(QPointingDevice::GrabTransition,QEventPoint))); + QSignalSpy scaleChangedSpy(pinchHandler, &QQuickPinchHandler::scaleChanged); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); QPoint p0(80, 80); QPoint p1(100, 100); - { - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); - pinchSequence.press(0, p0, window).commit(); - QQuickTouchUtils::flush(window); - // In order for the stationary point to remember its previous position, - // we have to reuse the same pinchSequence object. Otherwise if we let it - // be destroyed and then start a new sequence, point 0 will default to being - // stationary at 0, 0, and pinchHandler will filter out that touchpoint because - // it is outside its bounds. - pinchSequence.stationary(0).press(1, p1, window).commit(); - QQuickTouchUtils::flush(window); - - QPoint pd(10, 10); - // move one point until PinchHandler activates - for (int pi = 0; pi < 10 && !pinchHandler->active(); ++pi) { - p1 += pd; - pinchSequence.stationary(0).move(1, p1, window).commit(); - QQuickTouchUtils::flush(window); - } - QCOMPARE(pinchHandler->active(), true); - QLineF line(p0, p1); - const qreal startLength = line.length(); - - p1+=pd; - pinchSequence.stationary(0).move(1, p1, window).commit(); - QQuickTouchUtils::flush(window); - line.setP2(p1); - qreal scale = line.length() / startLength; - QVERIFY(qFloatDistance(root->property("scale").toReal(), scale) < 10); - QVERIFY(qFloatDistance(blackRect->scale(), scale) < 10); - - p1+=pd; - pinchSequence.stationary(0).move(1, p1, window).commit(); - QQuickTouchUtils::flush(window); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(&window, touchscreen.get()); + pinchSequence.press(0, p0, &window).commit(); + QQuickTouchUtils::flush(&window); + // In order for the stationary point to remember its previous position, + // we have to reuse the same pinchSequence object. Otherwise if we let it + // be destroyed and then start a new sequence, point 0 will default to being + // stationary at 0, 0, and pinchHandler will filter out that touchpoint because + // it is outside its bounds. + pinchSequence.stationary(0).press(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QCOMPARE(grabChangedSpy.size(), 1); // passive grab + + QPoint pd(10, 10); + // move one point until PinchHandler activates + for (int pi = 0; pi < 10 && !pinchHandler->active(); ++pi) { + p1 += pd; + pinchSequence.stationary(0).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + } + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QCOMPARE(pinchHandler->active(), true); + // grabs occur when the handler becomes active; at that time, QQuickHandlerPoint.sceneGrabPosition should be correct + QVERIFY(pinchHandler->firstPoint().sceneGrabPosition() != QPointF()); + QVERIFY(pinchHandler->lastPoint().sceneGrabPosition() != QPointF()); + QCOMPARE(pinchHandler->firstPoint().sceneGrabPosition(), pinchHandler->firstPoint().scenePosition()); + QCOMPARE(pinchHandler->lastPoint().sceneGrabPosition(), pinchHandler->lastPoint().scenePosition()); + // first point got a passive grab; both points got exclusive grabs + QCOMPARE(grabChangedSpy.size(), 3); + QLineF line(p0, p1); + const qreal startLength = line.length(); + + // move the same point even further and observe the change in scale + for (int i = 0; i < 2; ++i) { + qreal lastScale = pinchHandler->activeScale(); + p1 += pd; + pinchSequence.stationary(0).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); line.setP2(p1); - scale = line.length() / startLength; - - QVERIFY(qFloatDistance(root->property("scale").toReal(), scale) < 10); - QVERIFY(qFloatDistance(blackRect->scale(), scale) < 10); - - QPointF expectedCentroid = p0 + (p1 - p0)/2; + qreal expectedScale = line.length() / startLength; + qCDebug(lcPointerTests) << "pinchScale" << root->property("pinchScale").toReal() + << "expected" << expectedScale << "; target scale" << blackRect->scale() + << "increments" << scaleChangedSpy.size() + << "multiplier" << scaleChangedSpy.last().first().toReal(); + QVERIFY(qFloatDistance(root->property("pinchScale").toReal(), expectedScale) < 10); + QVERIFY(qFloatDistance(blackRect->scale(), expectedScale) < 10); + QCOMPARE(pinchHandler->persistentScale(), root->property("pinchScale").toReal()); + QCOMPARE(pinchHandler->persistentScale(), pinchHandler->activeScale()); // in sync for the first gesture + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), pinchHandler->activeScale()); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), pinchHandler->activeScale()); + const qreal expectedIncrement = pinchHandler->activeScale() / lastScale; + QCOMPARE(scaleChangedSpy.size(), i + 1); + QCOMPARE(scaleChangedSpy.last().first().toReal(), expectedIncrement); + QPointF expectedCentroid = p0 + (p1 - p0) / 2; QCOMPARE(pinchHandler->centroid().scenePosition(), expectedCentroid); } - // scale beyond bound - p1 += QPoint(20, 20); - { - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); - pinchSequence.stationary(0).move(1, p1, window).commit(); - QQuickTouchUtils::flush(window); - QCOMPARE(blackRect->scale(), qreal(4)); // qquickpinchhandler does not manipulate scale property - pinchSequence.release(0, p0, window).release(1, p1, window).commit(); - QQuickTouchUtils::flush(window); + qreal lastScale = pinchHandler->persistentScale(); + pinchSequence.release(0, p0, &window).release(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + // scale property is persistent after release + QCOMPARE(pinchHandler->persistentScale(), lastScale); + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), lastScale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), 1); + + // pinch a second time: scale picks up where we left off + p0 = QPoint(80, 80); + p1 = QPoint(100, 100); + pinchSequence.press(0, p0, &window).press(1, p1, &window).commit(); + // move one point until PinchHandler activates + for (int pi = 0; pi < 10 && !pinchHandler->active(); ++pi) { + p1 += pd; + pinchSequence.stationary(0).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); } + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QCOMPARE(pinchHandler->active(), true); + QCOMPARE(pinchHandler->persistentScale(), lastScale); // just activated, not scaling further yet + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), lastScale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), 1); + for (int i = 0; i < 2; ++i) { + lastScale = pinchHandler->persistentScale(); + p1 += pd; + pinchSequence.stationary(0).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QCOMPARE_GT(pinchHandler->persistentScale(), lastScale); + line.setP2(p1); + qreal expectedActiveScale = line.length() / startLength; + qCDebug(lcPointerTests) << i << "activeScale" << pinchHandler->activeScale() + << "expected" << expectedActiveScale << "; scale" << pinchHandler->persistentScale() + << "increments" << scaleChangedSpy.size() + << "multiplier" << scaleChangedSpy.last().first().toReal(); + QVERIFY(qFloatDistance(pinchHandler->activeScale(), expectedActiveScale) < 10); + QCOMPARE(pinchHandler->persistentScale(), root->property("pinchScale").toReal()); + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), root->property("pinchScale").toReal()); + QCOMPARE_NE(pinchHandler->persistentScale(), pinchHandler->activeScale()); // not in sync anymore + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), pinchHandler->activeScale()); + const qreal expectedIncrement = pinchHandler->persistentScale() / lastScale; + QCOMPARE(scaleChangedSpy.size(), i + 3); + QCOMPARE(scaleChangedSpy.last().first().toReal(), expectedIncrement); + } + + // scale beyond maximumScale + lastScale = pinchHandler->activeScale(); + p1 = QPoint(310, 310); + pinchSequence.stationary(0).move(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QCOMPARE(blackRect->scale(), qreal(4)); + QCOMPARE(pinchHandler->persistentScale(), qreal(4)); // limited by maximumScale + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), 4); + const qreal expectedIncrement = pinchHandler->activeScale() / lastScale; + QCOMPARE(scaleChangedSpy.size(), 5); + QCOMPARE(scaleChangedSpy.last().first().toReal(), expectedIncrement); + pinchSequence.release(0, p0, &window).release(1, p1, &window).commit(); + QQuickTouchUtils::flush(&window); QCOMPARE(pinchHandler->active(), false); } void tst_QQuickPinchHandler::scaleThreeFingers() { - QQuickView *window = createView(); + QQuickView *window = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(window); window->setSource(testFileUrl("threeFingers.qml")); window->show(); @@ -300,7 +385,7 @@ void tst_QQuickPinchHandler::scaleThreeFingers() QPoint p1(220, 80); QPoint p2(150, 220); { - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, touchscreen.get()); pinchSequence.press(0, p0, window).commit(); QQuickTouchUtils::flush(window); // In order for the stationary point to remember its previous position, @@ -322,9 +407,17 @@ void tst_QQuickPinchHandler::scaleThreeFingers() QCOMPARE(pinchHandler->active(), true); // scale we got was 1.1729088738267854364, but keep some slack - QVERIFY(withinBounds(1.163, root->property("scale").toReal(), 1.183)); + qCDebug(lcPointerTests) << "pinch scale" << pinchHandler->persistentScale() << "expected 1.173"; + QVERIFY(withinBounds(1.163, pinchHandler->persistentScale(), 1.183)); // should not rotate - QCOMPARE(root->property("rotation").toReal(), 0.); + QCOMPARE(root->rotation(), 0); + // rotation should be 0, but could be something tiny + qCDebug(lcPointerTests) << "pinch scale expected zero:" << pinchHandler->activeRotation() + << pinchHandler->rotationAxis()->activeValue() + << pinchHandler->rotationAxis()->persistentValue(); + QCOMPARE_LE(qAbs(pinchHandler->activeRotation()), 0.001); + QCOMPARE(pinchHandler->rotationAxis()->activeValue(), pinchHandler->activeRotation()); + QCOMPARE(pinchHandler->rotationAxis()->persistentValue(), 0); for (int i = 0; i < 5;++i) { p0 += QPoint(-4, -4); @@ -334,10 +427,10 @@ void tst_QQuickPinchHandler::scaleThreeFingers() QQuickTouchUtils::flush(window); } // scale we got was 1.4613, but keep some slack - QVERIFY(withinBounds(1.361, root->property("scale").toReal(), 1.561)); + QVERIFY(withinBounds(1.361, pinchHandler->persistentScale(), 1.561)); // since points were moved symetrically around the y axis, centroid should remain at x:150 - QCOMPARE(root->property("centroid").value<QQuickHandlerPoint>().scenePosition().x(), 150); // blackrect is at 50,50 + QCOMPARE(pinchHandler->centroid().scenePosition().x(), 150); // blackrect is at 50,50 // scale beyond bound, we should reach the maximumScale p0 += QPoint(-40, -40); @@ -346,16 +439,243 @@ void tst_QQuickPinchHandler::scaleThreeFingers() pinchSequence.move(0, p0,window).move(1, p1,window).move(2, p2,window).commit(); QQuickTouchUtils::flush(window); - QCOMPARE(root->property("scale").toReal(), 2.); + QCOMPARE(pinchHandler->persistentScale(), 2); + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), 2); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), 2); pinchSequence.release(0, p0, window).release(1, p1, window).release(2, p2, window).commit(); QQuickTouchUtils::flush(window); } QCOMPARE(pinchHandler->active(), false); } +void tst_QQuickPinchHandler::scaleNativeGesture_data() +{ + QTest::addColumn<QString>("qmlfile"); + QTest::addColumn<qreal>("scale"); + + QTest::newRow("just pinch") << "pinchproperties.qml" << 1.1; + QTest::newRow("pinch & drag") << "pinchAndDrag.qml" << 1.1; + QTest::newRow("bigger than limit") << "pinchproperties.qml" << 5.0; + QTest::newRow("smaller than limit") << "pinchproperties.qml" << 0.25; +} + +void tst_QQuickPinchHandler::scaleNativeGesture() +{ + QFETCH(QString, qmlfile); + QFETCH(qreal, scale); + + QQuickView *window = QQuickViewTestUtils::createView(); + QScopedPointer<QQuickView> scope(window); + window->setSource(testFileUrl(qmlfile)); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + QVERIFY(window->rootObject() != nullptr); + qApp->processEvents(); + + QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>("pinchHandler"); + QVERIFY(pinchHandler != nullptr); + QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject()); + QVERIFY(root != nullptr); + QQuickItem *target = window->rootObject()->findChild<QQuickItem*>("blackrect"); + QVERIFY(target != nullptr); + + QPointF targetPos = target->position(); + ulong ts = 1; + + // first pinch: scale it + const qreal expectedScale = qBound(qreal(0.5), scale, qreal(4)); + QPointF pinchPos(75, 75); + QPointF pinchLocalPos = target->mapFromScene(pinchPos); + // target position is adjusted in QQuickItemPrivate::adjustedPosForTransform() + // so as to compensate for the change in size, to hold the centroid in place + const QPointF expectedPos = targetPos + QPointF( (pinchPos.x() - target->x()) * (expectedScale - 1), + (pinchPos.y() - target->y()) * (expectedScale - 1) ); + QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad.get(), + Qt::BeginNativeGesture, pinchPos, pinchPos); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QWindowSystemInterface::handleGestureEventWithRealValue(window, ts++, touchpad.get(), + Qt::ZoomNativeGesture, scale - 1, pinchPos, pinchPos); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QTRY_COMPARE(target->scale(), expectedScale); + QCOMPARE(pinchHandler->active(), true); + qCDebug(lcPointerTests) << "centroid: local" << pinchHandler->centroid().position() + << "scene" << pinchHandler->centroid().scenePosition(); + QCOMPARE(pinchHandler->centroid().position().toPoint(), pinchLocalPos.toPoint()); + QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), pinchPos.toPoint()); + QVERIFY(qAbs(target->position().x() - expectedPos.x()) < 0.001); + QVERIFY(qAbs(target->position().y() - expectedPos.y()) < 0.001); + QCOMPARE(pinchHandler->persistentScale(), expectedScale); + QCOMPARE(pinchHandler->activeScale(), scale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), scale); + QCOMPARE(pinchHandler->activeTranslation(), QPointF()); + QCOMPARE(pinchHandler->activeRotation(), 0); + QCOMPARE(pinchHandler->rotationAxis()->persistentValue(), 0); + QCOMPARE(pinchHandler->rotationAxis()->activeValue(), 0); + QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad.get(), + Qt::EndNativeGesture, pinchPos, pinchPos); + QTRY_COMPARE(pinchHandler->active(), false); + QCOMPARE(target->scale(), expectedScale); + QCOMPARE(pinchHandler->persistentScale(), expectedScale); + QCOMPARE(pinchHandler->activeScale(), 1); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), 1); + QCOMPARE(pinchHandler->activeTranslation(), QPointF()); + QCOMPARE(pinchHandler->activeRotation(), 0); + QCOMPARE(pinchHandler->rotationAxis()->persistentValue(), 0); + QCOMPARE(pinchHandler->rotationAxis()->activeValue(), 0); + + // second pinch at a different position: scale it back to original size again + // but remove the limits first, so that we can scale arbitrarily + pinchHandler->scaleAxis()->setMaximum(qInf()); + pinchHandler->scaleAxis()->setMinimum(-qInf()); + const qreal reverseScale = (1 / expectedScale); + pinchPos = QPointF(110, 110); + pinchLocalPos = target->mapFromScene(pinchPos); + QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad.get(), + Qt::BeginNativeGesture, pinchPos, pinchPos); + QWindowSystemInterface::handleGestureEventWithRealValue(window, ts++, touchpad.get(), + Qt::ZoomNativeGesture, reverseScale - 1, pinchPos, pinchPos); + QTRY_COMPARE(target->scale(), 1); + QCOMPARE(pinchHandler->active(), true); + qCDebug(lcPointerTests) << "centroid: local" << pinchHandler->centroid().position() + << "scene" << pinchHandler->centroid().scenePosition(); + QCOMPARE(pinchHandler->centroid().position().toPoint(), pinchLocalPos.toPoint()); + QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), pinchPos.toPoint()); + QCOMPARE(pinchHandler->persistentScale(), 1); + QCOMPARE(pinchHandler->activeScale(), reverseScale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), reverseScale); + QWindowSystemInterface::handleGestureEvent(window, ts++, touchpad.get(), + Qt::EndNativeGesture, pinchPos, pinchPos); + QTRY_COMPARE(pinchHandler->active(), false); + QCOMPARE(target->scale(), 1); + QCOMPARE(pinchHandler->persistentScale(), 1); + QCOMPARE(pinchHandler->activeScale(), 1); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), 1); +} + +void tst_QQuickPinchHandler::cumulativeNativeGestures_data() +{ + QTest::addColumn<const QPointingDevice*>("device"); + QTest::addColumn<Qt::NativeGestureType>("gesture"); + QTest::addColumn<qreal>("value"); + QTest::addColumn<QList<QPoint>>("expectedTargetTranslations"); + + const auto *touchpadDevice = touchpad.get(); + const auto *mouse = QPointingDevice::primaryPointingDevice(); + + QTest::newRow("touchpad: rotate") << touchpadDevice << Qt::RotateNativeGesture << 5.0 + << QList<QPoint>{{-2, 2}, {-5, 4}, {-7, 6}, {-10, 7}}; + QTest::newRow("touchpad: scale") << touchpadDevice << Qt::ZoomNativeGesture << 0.1 + << QList<QPoint>{{3, 3}, {5, 5}, {8, 8}, {12, 12}}; + if (mouse->type() == QInputDevice::DeviceType::Mouse) { + QTest::newRow("mouse: rotate") << mouse << Qt::RotateNativeGesture << 5.0 + << QList<QPoint>{{-2, 2}, {-5, 4}, {-7, 6}, {-10, 7}}; + QTest::newRow("mouse: scale") << mouse << Qt::ZoomNativeGesture << 0.1 + << QList<QPoint>{{3, 3}, {5, 5}, {8, 8}, {12, 12}}; + } else { + qCWarning(lcPointerTests) << "skipping mouse tests: primary device is not a mouse" << mouse; + } +} + +void tst_QQuickPinchHandler::cumulativeNativeGestures() +{ + QFETCH(const QPointingDevice*, device); + QFETCH(Qt::NativeGestureType, gesture); + QFETCH(qreal, value); + QFETCH(QList<QPoint>, expectedTargetTranslations); + + QCOMPARE(expectedTargetTranslations.size(), 4); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("pinchproperties.qml"))); + QVERIFY(window.rootObject() != nullptr); + qApp->processEvents(); + + QQuickItem *root = qobject_cast<QQuickItem*>(window.rootObject()); + QVERIFY(root != nullptr); + QQuickPinchHandler *pinchHandler = root->findChild<QQuickPinchHandler*>("pinchHandler"); + QVERIFY(pinchHandler != nullptr); + QQuickItem *target = root->findChild<QQuickItem*>("blackrect"); + QVERIFY(target != nullptr); + QCOMPARE(pinchHandler->target(), target); + + ulong ts = 1; + qreal expectedScale = 1; + qreal expectedRotation = 0; + QPointF pinchPos(75, 75); + const QPointF initialTargetPos(target->position()); + QWindowSystemInterface::handleGestureEvent(&window, ts++, device, + Qt::BeginNativeGesture, pinchPos, pinchPos); + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + for (int i = 1; i <= 4; ++i) { + QWindowSystemInterface::handleGestureEventWithRealValue(&window, ts++, device, + gesture, value, pinchPos, pinchPos); + qApp->processEvents(); + switch (gesture) { + case Qt::ZoomNativeGesture: + expectedScale = qBound(qreal(0.5), qPow(1 + value, i), qreal(4)); + break; + case Qt::RotateNativeGesture: + expectedRotation = qBound(qreal(0), value * i, qreal(90)); + break; + default: + break; // PinchHandler doesn't react to the others + } + + qCDebug(lcPointerTests) << i << gesture << "with value" << value + << ": scale" << target->scale() << "expected" << expectedScale + << ": rotation" << target->rotation() << "expected" << expectedRotation; + if (lcPointerTests().isDebugEnabled()) QTest::qWait(500); + QCOMPARE(target->scale(), expectedScale); + QCOMPARE(target->rotation(), expectedRotation); + QCOMPARE(pinchHandler->persistentScale(), expectedScale); + QCOMPARE(pinchHandler->activeScale(), expectedScale); + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), expectedScale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), expectedScale); + QCOMPARE(pinchHandler->persistentRotation(), expectedRotation); + QCOMPARE(pinchHandler->activeRotation(), expectedRotation); + QCOMPARE(pinchHandler->rotationAxis()->persistentValue(), expectedRotation); + QCOMPARE(pinchHandler->rotationAxis()->activeValue(), expectedRotation); + // The target gets transformed around the gesture position, for which + // QQuickItemPrivate::adjustedPosForTransform() computes its new position to compensate. + QPointF delta = target->position() - initialTargetPos; + qCDebug(lcPointerTests) << "target moved by" << delta << "to" << target->position() + << "active trans" << pinchHandler->activeTranslation() + << "perst trans" << pinchHandler->persistentTranslation(); + QCOMPARE_NE(target->position(), initialTargetPos); + QCOMPARE(delta.toPoint(), expectedTargetTranslations.at(i - 1)); + // The native pinch gesture cannot include a translation component (and + // the cursor doesn't move while you are performing the gesture on a touchpad). + QCOMPARE(pinchHandler->activeTranslation(), QPointF()); + // The target only moves to compensate for scale and rotation changes, and that's + // not reflected in PinchHandler.persistentTranslation. + QCOMPARE(pinchHandler->persistentTranslation(), QPointF()); + } + QCOMPARE(pinchHandler->active(), true); + qCDebug(lcPointerTests) << "centroid: local" << pinchHandler->centroid().position() + << "scene" << pinchHandler->centroid().scenePosition(); + QCOMPARE(pinchHandler->persistentScale(), expectedScale); + QCOMPARE(pinchHandler->activeScale(), expectedScale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), expectedScale); + QWindowSystemInterface::handleGestureEvent(&window, ts++, device, + Qt::EndNativeGesture, pinchPos, pinchPos); + QTRY_COMPARE(pinchHandler->active(), false); + QCOMPARE(target->scale(), expectedScale); + QCOMPARE(target->rotation(), expectedRotation); + QCOMPARE(pinchHandler->persistentScale(), expectedScale); + QCOMPARE(pinchHandler->activeScale(), 1); + QCOMPARE(pinchHandler->scaleAxis()->persistentValue(), expectedScale); + QCOMPARE(pinchHandler->scaleAxis()->activeValue(), 1); + QCOMPARE(pinchHandler->persistentRotation(), expectedRotation); + QCOMPARE(pinchHandler->activeRotation(), 0); + QCOMPARE(pinchHandler->rotationAxis()->persistentValue(), expectedRotation); + QCOMPARE(pinchHandler->rotationAxis()->activeValue(), 0); + QCOMPARE(pinchHandler->activeTranslation(), QPointF()); + QCOMPARE(pinchHandler->persistentTranslation(), QPointF()); +} + void tst_QQuickPinchHandler::pan() { - QQuickView *window = createView(); + QQuickView *window = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(window); window->setSource(testFileUrl("pinchproperties.qml")); window->show(); @@ -365,6 +685,7 @@ void tst_QQuickPinchHandler::pan() QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>("pinchHandler"); QVERIFY(pinchHandler != nullptr); + QSignalSpy translationChangedSpy(pinchHandler, &QQuickPinchHandler::translationChanged); QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject()); QVERIFY(root != nullptr); @@ -377,7 +698,7 @@ void tst_QQuickPinchHandler::pan() QPoint p1(100, 100); { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, touchscreen.get()); pinchSequence.press(0, p0, window).commit(); QQuickTouchUtils::flush(window); // In order for the stationary point to remember its previous position, @@ -385,7 +706,7 @@ void tst_QQuickPinchHandler::pan() pinchSequence.stationary(0).press(1, p1, window).commit(); QQuickTouchUtils::flush(window); QVERIFY(!root->property("pinchActive").toBool()); - QCOMPARE(root->property("scale").toReal(), -1.0); + QCOMPARE(root->property("pinchScale").toReal(), -1.0); p0 += QPoint(dragThreshold, 0); p1 += QPoint(dragThreshold, 0); @@ -393,7 +714,7 @@ void tst_QQuickPinchHandler::pan() QQuickTouchUtils::flush(window); // movement < dragThreshold: pinchHandler not yet active QVERIFY(!root->property("pinchActive").toBool()); - QCOMPARE(root->property("scale").toReal(), -1.0); + QCOMPARE(root->property("pinchScale").toReal(), -1.0); // just above the dragThreshold: pinchHandler starts p0 += QPoint(1, 0); @@ -401,7 +722,7 @@ void tst_QQuickPinchHandler::pan() pinchSequence.move(0, p0, window).move(1, p1, window).commit(); QQuickTouchUtils::flush(window); QCOMPARE(pinchHandler->active(), true); - QCOMPARE(root->property("scale").toReal(), 1.0); + QCOMPARE(root->property("pinchScale").toReal(), 1.0); // Calculation of the center point is tricky at first: // center point of the two touch points in item coordinates: @@ -413,6 +734,8 @@ void tst_QQuickPinchHandler::pan() // blackrect starts at 50,50 QCOMPARE(blackRect->x(), 50.0); QCOMPARE(blackRect->y(), 50.0); + QCOMPARE(translationChangedSpy.size(), 1); + QCOMPARE(translationChangedSpy.first().first().value<QVector2D>(), QVector2D(0, 0)); p0 += QPoint(10, 0); p1 += QPoint(10, 0); @@ -421,6 +744,8 @@ void tst_QQuickPinchHandler::pan() QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 11, 90)); QCOMPARE(blackRect->x(), 60.0); QCOMPARE(blackRect->y(), 50.0); + QCOMPARE(translationChangedSpy.size(), 2); + QCOMPARE(translationChangedSpy.last().first().value<QVector2D>(), QVector2D(10, 0)); p0 += QPoint(0, 10); p1 += QPoint(0, 10); @@ -429,6 +754,8 @@ void tst_QQuickPinchHandler::pan() QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 11, 90 + 10)); QCOMPARE(blackRect->x(), 60.0); QCOMPARE(blackRect->y(), 60.0); + QCOMPARE(translationChangedSpy.size(), 3); + QCOMPARE(translationChangedSpy.last().first().value<QVector2D>(), QVector2D(0, 10)); p0 += QPoint(10, 10); p1 += QPoint(10, 10); @@ -438,18 +765,22 @@ void tst_QQuickPinchHandler::pan() QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 21, 90 + 20)); QCOMPARE(blackRect->x(), 70.0); QCOMPARE(blackRect->y(), 70.0); + QCOMPARE(translationChangedSpy.size(), 4); + QCOMPARE(translationChangedSpy.last().first().value<QVector2D>(), QVector2D(10, 10)); } // pan x beyond bound p0 += QPoint(100,100); p1 += QPoint(100,100); - QTest::touchEvent(window, device).move(0, p0, window).move(1, p1, window); + QTest::touchEvent(window, touchscreen.get()).move(0, p0, window).move(1, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(blackRect->x(), 140.0); QCOMPARE(blackRect->y(), 170.0); + QCOMPARE(translationChangedSpy.size(), 5); + QCOMPARE(translationChangedSpy.last().first().value<QVector2D>(), QVector2D(100, 100)); - QTest::touchEvent(window, device).release(0, p0, window).release(1, p1, window); + QTest::touchEvent(window, touchscreen.get()).release(0, p0, window).release(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(!root->property("pinchActive").toBool()); } @@ -467,7 +798,7 @@ void tst_QQuickPinchHandler::dragAxesEnabled_data() void tst_QQuickPinchHandler::dragAxesEnabled() { - QQuickView *window = createView(); + QQuickView *window = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(window); window->setSource(testFileUrl("pinchproperties.qml")); window->show(); @@ -489,7 +820,7 @@ void tst_QQuickPinchHandler::dragAxesEnabled() QPoint blackRectPos = blackRect->position().toPoint(); // press two points, one above the rectangle's center and one below - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, touchscreen.get()); pinchSequence.press(0, p0, window).press(1, p1, window).commit(); QQuickTouchUtils::flush(window); @@ -526,7 +857,7 @@ void tst_QQuickPinchHandler::dragAxesEnabled() QCOMPARE(blackRect->position().toPoint().x(), xEnabled ? 140 : blackRectPos.x()); // because of xAxis.maximum QCOMPARE(blackRect->position().toPoint().y(), yEnabled ? 170 : blackRectPos.y()); // because of yAxis.maximum - QTest::touchEvent(window, device).release(0, p0, window).release(1, p1, window); + QTest::touchEvent(window, touchscreen.get()).release(0, p0, window).release(1, p1, window); QQuickTouchUtils::flush(window); } @@ -534,7 +865,7 @@ void tst_QQuickPinchHandler::dragAxesEnabled() void tst_QQuickPinchHandler::retouch() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); - QQuickView *window = createView(); + QQuickView *window = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(window); window->setSource(testFileUrl("pinchproperties.qml")); window->show(); @@ -555,7 +886,7 @@ void tst_QQuickPinchHandler::retouch() QPoint p0(80, 80); QPoint p1(100, 100); { - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, touchscreen.get()); pinchSequence.press(0, p0, window).commit(); QQuickTouchUtils::flush(window); // In order for the stationary point to remember its previous position, @@ -568,7 +899,7 @@ void tst_QQuickPinchHandler::retouch() pinchSequence.move(0, p0,window).move(1, p1,window).commit(); QQuickTouchUtils::flush(window); - QCOMPARE(root->property("scale").toReal(), 1.0); + QCOMPARE(root->property("pinchScale").toReal(), 1.0); QCOMPARE(pinchHandler->active(), true); p0 -= delta; @@ -579,8 +910,8 @@ void tst_QQuickPinchHandler::retouch() QCOMPARE(pinchHandler->active(), true); // accept some slack - QVERIFY(withinBounds(1.4, root->property("scale").toReal(), 1.6)); - QCOMPARE(pinchHandler->centroid().position(), QPointF(40, 40)); // blackrect is at 50,50 + QVERIFY(withinBounds(1.4, root->property("pinchScale").toReal(), 1.6)); + QCOMPARE(pinchHandler->centroid().position().toPoint(), QPoint(40, 40)); // blackrect is at 50,50 QVERIFY(withinBounds(1.4, blackRect->scale(), 1.6)); QCOMPARE(root->property("activeCount").toInt(), 1); @@ -617,7 +948,7 @@ void tst_QQuickPinchHandler::retouch() void tst_QQuickPinchHandler::cancel() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); - QQuickView *window = createView(); + QQuickView *window = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(window); window->setSource(testFileUrl("pinchproperties.qml")); window->show(); @@ -638,7 +969,7 @@ void tst_QQuickPinchHandler::cancel() QPoint p0(80, 80); QPoint p1(100, 100); { - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, touchscreen.get()); pinchSequence.press(0, p0, window).commit(); QQuickTouchUtils::flush(window); // In order for the stationary point to remember its previous position, @@ -654,7 +985,7 @@ void tst_QQuickPinchHandler::cancel() pinchSequence.move(0, p0,window).move(1, p1,window).commit(); QQuickTouchUtils::flush(window); - QCOMPARE(root->property("scale").toReal(), 1.0); + QCOMPARE(root->property("pinchScale").toReal(), 1.0); QCOMPARE(pinchHandler->active(), true); p0 -= delta; @@ -662,18 +993,18 @@ void tst_QQuickPinchHandler::cancel() pinchSequence.move(0, p0,window).move(1, p1,window).commit(); QQuickTouchUtils::flush(window); - QVERIFY(withinBounds(1.4, root->property("scale").toReal(), 1.6)); - QCOMPARE(pinchHandler->centroid().position(), QPointF(40, 40)); // blackrect is at 50,50 + QVERIFY(withinBounds(1.4, root->property("pinchScale").toReal(), 1.6)); + QCOMPARE(pinchHandler->centroid().position().toPoint(), QPoint(40, 40)); // blackrect is at 50,50 QVERIFY(withinBounds(1.4, blackRect->scale(), 1.6)); QSKIP("cancel is not supported atm"); - QTouchEvent cancelEvent(QEvent::TouchCancel, device); + QTouchEvent cancelEvent(QEvent::TouchCancel, touchscreen.get()); QCoreApplication::sendEvent(window, &cancelEvent); QQuickTouchUtils::flush(window); - QCOMPARE(root->property("scale").toReal(), 1.0); - QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50 + QCOMPARE(root->property("pinchScale").toReal(), 1.0); + QCOMPARE(root->property("center").toPoint(), QPoint(40, 40)); // blackrect is at 50,50 QCOMPARE(blackRect->scale(), 1.0); QVERIFY(!root->property("pinchActive").toBool()); } @@ -710,7 +1041,7 @@ void tst_QQuickPinchHandler::transformedpinchHandler() QFETCH(QPoint, p1); QFETCH(bool, shouldPinch); - QQuickView *view = createView(); + QQuickView *view = QQuickViewTestUtils::createView(); QScopedPointer<QQuickView> scope(view); view->setSource(testFileUrl("transformedPinchHandler.qml")); view->show(); @@ -724,7 +1055,7 @@ void tst_QQuickPinchHandler::transformedpinchHandler() const int threshold = qApp->styleHints()->startDragDistance(); { - QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(view, device); + QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(view, touchscreen.get()); // start pinchHandler pinchSequence.press(0, p0, view).commit(); QQuickTouchUtils::flush(view); @@ -749,14 +1080,6 @@ void tst_QQuickPinchHandler::transformedpinchHandler() } } -QQuickView *tst_QQuickPinchHandler::createView() -{ - QQuickView *window = new QQuickView(0); - window->setGeometry(0,0,240,320); - - return window; -} - QTEST_MAIN(tst_QQuickPinchHandler) #include "tst_qquickpinchhandler.moc" |