1086 lines
49 KiB
C++
1086 lines
49 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include <QtTest/QtTest>
|
|
#include <QtTest/QSignalSpy>
|
|
#include <QtGui/QStyleHints>
|
|
#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 <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() : 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();
|
|
void retouch();
|
|
void cancel();
|
|
void transformedpinchHandler_data();
|
|
void transformedpinchHandler();
|
|
|
|
private:
|
|
QScopedPointer<QPointingDevice> touchscreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice());
|
|
QScopedPointer<QPointingDevice> touchpad = QScopedPointer<QPointingDevice>(QTest::createTouchDevice(QInputDevice::DeviceType::TouchPad));
|
|
};
|
|
|
|
void tst_QQuickPinchHandler::cleanupTestCase()
|
|
{
|
|
|
|
}
|
|
|
|
static bool withinBounds(qreal lower, qreal num, qreal upper)
|
|
{
|
|
return num >= lower && num <= upper;
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::pinchProperties()
|
|
{
|
|
QScopedPointer<QQuickView> window(QQuickViewTestUtils::createView());
|
|
window->setSource(testFileUrl("pinchproperties.qml"));
|
|
window->show();
|
|
QVERIFY(window->rootObject() != nullptr);
|
|
|
|
QQuickPinchHandler *pinchHandler = window->rootObject()->findChild<QQuickPinchHandler*>("pinchHandler");
|
|
QVERIFY(pinchHandler != nullptr);
|
|
|
|
// target
|
|
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
|
|
QVERIFY(blackRect != nullptr);
|
|
QCOMPARE(blackRect, pinchHandler->target());
|
|
QQuickItem *rootItem = qobject_cast<QQuickItem*>(window->rootObject());
|
|
QVERIFY(rootItem != nullptr);
|
|
QSignalSpy targetSpy(pinchHandler, SIGNAL(targetChanged()));
|
|
pinchHandler->setTarget(rootItem);
|
|
QCOMPARE(targetSpy.size(),1);
|
|
pinchHandler->setTarget(rootItem);
|
|
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->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->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 scaleAxisMinSpy(pinchHandler->scaleAxis(), &QQuickDragAxis::minimumChanged);
|
|
QSignalSpy scaleAxisMaxSpy(pinchHandler->scaleAxis(), &QQuickDragAxis::maximumChanged);
|
|
|
|
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(), 4);
|
|
QT_WARNING_POP
|
|
#endif
|
|
|
|
pinchHandler->scaleAxis()->setMinimum(0.25);
|
|
pinchHandler->scaleAxis()->setMaximum(1.5);
|
|
|
|
QCOMPARE(pinchHandler->scaleAxis()->minimum(), 0.25);
|
|
QCOMPARE(pinchHandler->scaleAxis()->maximum(), 1.5);
|
|
|
|
#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(scaleAxisMinSpy.size(),1);
|
|
QCOMPARE(scaleAxisMaxSpy.size(),1);
|
|
|
|
pinchHandler->scaleAxis()->setMinimum(0.25);
|
|
pinchHandler->scaleAxis()->setMaximum(1.5);
|
|
|
|
QCOMPARE(scaleAxisMinSpy.size(),1);
|
|
QCOMPARE(scaleAxisMaxSpy.size(),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);
|
|
}
|
|
|
|
QEventPoint makeTouchPoint(int id, QPoint p, QQuickView *v, QQuickItem *i)
|
|
{
|
|
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_data()
|
|
{
|
|
QTest::addColumn<QUrl>("qmlfile");
|
|
QTest::addColumn<bool>("hasTarget");
|
|
QTest::newRow("targetModifying") << testFileUrl("pinchproperties.qml") << true;
|
|
QTest::newRow("nullTarget") << testFileUrl("nullTarget.qml") << false;
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::scale()
|
|
{
|
|
QFETCH(QUrl, qmlfile);
|
|
QFETCH(bool, hasTarget);
|
|
|
|
QQuickView window;
|
|
QVERIFY(QQuickTest::showView(window, qmlfile));
|
|
QQuickItem *root = qobject_cast<QQuickItem*>(window.rootObject());
|
|
QVERIFY(root != nullptr);
|
|
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, 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);
|
|
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);
|
|
}
|
|
|
|
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 = QQuickViewTestUtils::createView();
|
|
QScopedPointer<QQuickView> scope(window);
|
|
window->setSource(testFileUrl("threeFingers.qml"));
|
|
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);
|
|
|
|
// target
|
|
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
|
|
QVERIFY(blackRect != nullptr);
|
|
|
|
// center of blackrect is at 150,150
|
|
QPoint p0(80, 80);
|
|
QPoint p1(220, 80);
|
|
QPoint p2(150, 220);
|
|
{
|
|
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);
|
|
pinchSequence.stationary(0).stationary(1).press(2, p2, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
for (int i = 0; i < 5;++i) {
|
|
p0 += QPoint(-4, -4);
|
|
p1 += QPoint(+4, -4);
|
|
p2 += QPoint( 0, +6);
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).move(2, p2,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
}
|
|
|
|
QCOMPARE(pinchHandler->active(), true);
|
|
// scale we got was 1.1729088738267854364, but keep some slack
|
|
qCDebug(lcPointerTests) << "pinch scale" << pinchHandler->persistentScale() << "expected 1.173";
|
|
QVERIFY(withinBounds(1.163, pinchHandler->persistentScale(), 1.183));
|
|
// should not rotate
|
|
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);
|
|
p1 += QPoint(+4, -4);
|
|
p2 += QPoint( 0, +6);
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).move(2, p2,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
}
|
|
// scale we got was 1.4613, but keep some slack
|
|
QVERIFY(withinBounds(1.361, pinchHandler->persistentScale(), 1.561));
|
|
|
|
// since points were moved symetrically around the y axis, centroid should remain at x:150
|
|
QCOMPARE(pinchHandler->centroid().scenePosition().x(), 150); // blackrect is at 50,50
|
|
|
|
// scale beyond bound, we should reach the maximumScale
|
|
p0 += QPoint(-40, -40);
|
|
p1 += QPoint(+40, -40);
|
|
p2 += QPoint( 0, +60);
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).move(2, p2,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
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 = QQuickViewTestUtils::createView();
|
|
QScopedPointer<QQuickView> scope(window);
|
|
window->setSource(testFileUrl("pinchproperties.qml"));
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QVERIFY(window->rootObject() != nullptr);
|
|
qApp->processEvents();
|
|
|
|
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);
|
|
|
|
// target
|
|
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
|
|
QVERIFY(blackRect != nullptr);
|
|
|
|
QPoint p0(80, 80);
|
|
QPoint p1(100, 100);
|
|
{
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
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.
|
|
pinchSequence.stationary(0).press(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QVERIFY(!root->property("pinchActive").toBool());
|
|
QCOMPARE(root->property("pinchScale").toReal(), -1.0);
|
|
|
|
p0 += QPoint(dragThreshold, 0);
|
|
p1 += QPoint(dragThreshold, 0);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
// movement < dragThreshold: pinchHandler not yet active
|
|
QVERIFY(!root->property("pinchActive").toBool());
|
|
QCOMPARE(root->property("pinchScale").toReal(), -1.0);
|
|
|
|
// just above the dragThreshold: pinchHandler starts
|
|
p0 += QPoint(1, 0);
|
|
p1 += QPoint(1, 0);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(pinchHandler->active(), true);
|
|
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:
|
|
// scene coordinates: (80, 80) + (dragThreshold, 0), (100, 100) + (dragThreshold, 0)
|
|
// = ((180+dT)/2, 180/2) = (90+dT, 90)
|
|
// item coordinates: (scene) - (50, 50) = (40+dT, 40)
|
|
QCOMPARE(pinchHandler->centroid().scenePosition(), QPointF(90 + dragThreshold + 1, 90));
|
|
// pan started, but no actual movement registered yet:
|
|
// 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);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
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);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
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);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
// now the item moved again, thus the center point of the touch is moved in total by (10, 10)
|
|
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, 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, touchscreen.get()).release(0, p0, window).release(1, p1, window);
|
|
QQuickTouchUtils::flush(window);
|
|
QVERIFY(!root->property("pinchActive").toBool());
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::dragAxesEnabled_data()
|
|
{
|
|
QTest::addColumn<bool>("xEnabled");
|
|
QTest::addColumn<bool>("yEnabled");
|
|
|
|
QTest::newRow("both enabled") << true << true;
|
|
QTest::newRow("x enabled") << true << false;
|
|
QTest::newRow("y enabled") << false << true;
|
|
QTest::newRow("both disabled") << false << false;
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::dragAxesEnabled()
|
|
{
|
|
QQuickView *window = QQuickViewTestUtils::createView();
|
|
QScopedPointer<QQuickView> scope(window);
|
|
window->setSource(testFileUrl("pinchproperties.qml"));
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QVERIFY(window->rootObject() != nullptr);
|
|
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
|
|
QVERIFY(blackRect != nullptr);
|
|
QQuickPinchHandler *pinchHandler = blackRect->findChild<QQuickPinchHandler*>();
|
|
QVERIFY(pinchHandler != nullptr);
|
|
|
|
QFETCH(bool, xEnabled);
|
|
QFETCH(bool, yEnabled);
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
pinchHandler->xAxis()->setEnabled(xEnabled);
|
|
pinchHandler->yAxis()->setEnabled(yEnabled);
|
|
QPoint c = blackRect->mapToScene(blackRect->clipRect().center()).toPoint();
|
|
QPoint p0 = c - QPoint(0, dragThreshold);
|
|
QPoint p1 = c + QPoint(0, dragThreshold);
|
|
QPoint blackRectPos = blackRect->position().toPoint();
|
|
|
|
// press two points, one above the rectangle's center and one below
|
|
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, touchscreen.get());
|
|
pinchSequence.press(0, p0, window).press(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
// expand the pinch vertically
|
|
p0 -= QPoint(0, dragThreshold);
|
|
p1 += QPoint(0, dragThreshold);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
for (int pi = 0; pi < 4; ++pi) {
|
|
p0 -= QPoint(0, dragThreshold);
|
|
p1 += QPoint(0, dragThreshold);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
qCDebug(lcPointerTests) << pi << "active" << pinchHandler->active() << "pts" << p0 << p1
|
|
<< "centroid" << pinchHandler->centroid().scenePosition()
|
|
<< "rect pos" << blackRect->position() << "scale" << blackRect->scale();
|
|
}
|
|
QCOMPARE(pinchHandler->active(), true);
|
|
QVERIFY(blackRect->scale() >= 2.0);
|
|
// drag started, but we only did scaling without any translation
|
|
QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), c);
|
|
QCOMPARE(blackRect->position().toPoint().x(), blackRectPos.x());
|
|
QCOMPARE(blackRect->position().toPoint().y(), blackRectPos.y());
|
|
|
|
// drag diagonally
|
|
p0 += QPoint(150, 150);
|
|
p1 += QPoint(150, 150);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
// the target should move if the xAxis is enabled, or stay in place if not
|
|
qCDebug(lcPointerTests) << "after diagonal drag: pts" << p0 << p1
|
|
<< "centroid" << pinchHandler->centroid().scenePosition()
|
|
<< "rect pos" << blackRect->position() << "scale" << blackRect->scale();
|
|
QCOMPARE(pinchHandler->centroid().scenePosition().toPoint(), QPoint(250, 250));
|
|
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, touchscreen.get()).release(0, p0, window).release(1, p1, window);
|
|
QQuickTouchUtils::flush(window);
|
|
}
|
|
|
|
// test pinchHandler, release one point, touch again to continue pinchHandler
|
|
void tst_QQuickPinchHandler::retouch()
|
|
{
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
QQuickView *window = QQuickViewTestUtils::createView();
|
|
QScopedPointer<QQuickView> scope(window);
|
|
window->setSource(testFileUrl("pinchproperties.qml"));
|
|
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);
|
|
|
|
// target
|
|
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
|
|
QVERIFY(blackRect != nullptr);
|
|
|
|
QPoint p0(80, 80);
|
|
QPoint p1(100, 100);
|
|
{
|
|
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.
|
|
pinchSequence.stationary(0).press(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
const QPoint delta(dragThreshold + 1, dragThreshold + 1);
|
|
p0 -= delta;
|
|
p1 += delta;
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QCOMPARE(root->property("pinchScale").toReal(), 1.0);
|
|
QCOMPARE(pinchHandler->active(), true);
|
|
|
|
p0 -= delta;
|
|
p1 += delta;
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QCOMPARE(pinchHandler->active(), true);
|
|
|
|
// accept some slack
|
|
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);
|
|
QCOMPARE(root->property("deactiveCount").toInt(), 0);
|
|
|
|
// Hold down the first finger but release the second one
|
|
pinchSequence.stationary(0).release(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QCOMPARE(root->property("activeCount").toInt(), 1);
|
|
QCOMPARE(root->property("deactiveCount").toInt(), 1);
|
|
|
|
// Keep holding down the first finger and re-touch the second one, then move them both
|
|
pinchSequence.stationary(0).press(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
p0 -= QPoint(10,10);
|
|
p1 += QPoint(10,10);
|
|
pinchSequence.move(0, p0, window).move(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
// Lifting and retouching results in onPinchStarted being called again
|
|
QCOMPARE(root->property("activeCount").toInt(), 2);
|
|
QCOMPARE(root->property("deactiveCount").toInt(), 1);
|
|
|
|
pinchSequence.release(0, p0, window).release(1, p1, window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QCOMPARE(pinchHandler->active(), false);
|
|
QCOMPARE(root->property("activeCount").toInt(), 2);
|
|
QCOMPARE(root->property("deactiveCount").toInt(), 2);
|
|
}
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::cancel()
|
|
{
|
|
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
|
|
QQuickView *window = QQuickViewTestUtils::createView();
|
|
QScopedPointer<QQuickView> scope(window);
|
|
window->setSource(testFileUrl("pinchproperties.qml"));
|
|
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);
|
|
|
|
// target
|
|
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
|
|
QVERIFY(blackRect != nullptr);
|
|
|
|
QPoint p0(80, 80);
|
|
QPoint p1(100, 100);
|
|
{
|
|
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);
|
|
const QPoint delta(dragThreshold + 1, dragThreshold + 1);
|
|
p0 -= delta;
|
|
p1 += delta;
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
QCOMPARE(root->property("pinchScale").toReal(), 1.0);
|
|
QCOMPARE(pinchHandler->active(), true);
|
|
|
|
p0 -= delta;
|
|
p1 += delta;
|
|
pinchSequence.move(0, p0,window).move(1, p1,window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
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, touchscreen.get());
|
|
QCoreApplication::sendEvent(window, &cancelEvent);
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::transformedpinchHandler_data()
|
|
{
|
|
QTest::addColumn<QPoint>("p0");
|
|
QTest::addColumn<QPoint>("p1");
|
|
QTest::addColumn<bool>("shouldPinch");
|
|
|
|
QTest::newRow("checking inner pinchHandler 1")
|
|
<< QPoint(200, 140) << QPoint(200, 260) << true;
|
|
|
|
QTest::newRow("checking inner pinchHandler 2")
|
|
<< QPoint(140, 200) << QPoint(200, 140) << true;
|
|
|
|
QTest::newRow("checking inner pinchHandler 3")
|
|
<< QPoint(140, 200) << QPoint(260, 200) << true;
|
|
|
|
QTest::newRow("checking outer pinchHandler 1")
|
|
<< QPoint(140, 140) << QPoint(260, 260) << false;
|
|
|
|
QTest::newRow("checking outer pinchHandler 2")
|
|
<< QPoint(140, 140) << QPoint(200, 200) << false;
|
|
|
|
QTest::newRow("checking outer pinchHandler 3")
|
|
<< QPoint(140, 260) << QPoint(260, 260) << false;
|
|
}
|
|
|
|
void tst_QQuickPinchHandler::transformedpinchHandler()
|
|
{
|
|
QFETCH(QPoint, p0);
|
|
QFETCH(QPoint, p1);
|
|
QFETCH(bool, shouldPinch);
|
|
|
|
QQuickView *view = QQuickViewTestUtils::createView();
|
|
QScopedPointer<QQuickView> scope(view);
|
|
view->setSource(testFileUrl("transformedPinchHandler.qml"));
|
|
view->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(view));
|
|
QVERIFY(view->rootObject() != nullptr);
|
|
qApp->processEvents();
|
|
|
|
QQuickPinchHandler *pinchHandler = view->rootObject()->findChild<QQuickPinchHandler*>("pinchHandler");
|
|
QVERIFY(pinchHandler != nullptr);
|
|
|
|
const int threshold = qApp->styleHints()->startDragDistance();
|
|
|
|
{
|
|
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(view, touchscreen.get());
|
|
// start pinchHandler
|
|
pinchSequence.press(0, p0, view).commit();
|
|
QQuickTouchUtils::flush(view);
|
|
// In order for the stationary point to remember its previous position,
|
|
// we have to reuse the same pinchSequence object.
|
|
pinchSequence.stationary(0).press(1, p1, view).commit();
|
|
QQuickTouchUtils::flush(view);
|
|
|
|
// we move along the line that the two points form.
|
|
// The distance we move should be above the threshold (threshold * 2 to be safe)
|
|
QVector2D delta(p1 - p0);
|
|
delta.normalize();
|
|
QVector2D movement = delta * (threshold * 2);
|
|
pinchSequence.stationary(0).move(1, p1 + movement.toPoint(), view).commit();
|
|
QQuickTouchUtils::flush(view);
|
|
QCOMPARE(pinchHandler->active(), shouldPinch);
|
|
|
|
// release pinchHandler
|
|
pinchSequence.release(0, p0, view).release(1, p1, view).commit();
|
|
QQuickTouchUtils::flush(view);
|
|
QCOMPARE(pinchHandler->active(), false);
|
|
}
|
|
}
|
|
|
|
QTEST_MAIN(tst_QQuickPinchHandler)
|
|
|
|
#include "tst_qquickpinchhandler.moc"
|