Begin testing QQDeliveryAgent subscenes

The first test is passiveGrabberOrder(), to verify that if the user
interacts with handlers in a subscene and the root scene simultaneously,
the passive grab order and the order of the signals from the handlers
is deterministic: the subscene goes first because it's conceptually
"on top".

Change-Id: Id00ab1497fbd3c9d7afa02f8098699bd569ded70
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Shawn Rutledge 2021-04-29 16:47:23 +02:00
parent 1dc4bdbabf
commit 152e12dc22
5 changed files with 270 additions and 0 deletions

View File

@ -30,6 +30,7 @@ if(QT_FEATURE_private_tests)
add_subdirectory(qquickdynamicpropertyanimation)
add_subdirectory(qquickborderimage)
add_subdirectory(qquickwindow)
add_subdirectory(qquickdeliveryagent)
add_subdirectory(qquickdrag)
add_subdirectory(qquickdroparea)
add_subdirectory(qquickflickable)

View File

@ -0,0 +1,42 @@
#####################################################################
## tst_qquickdeliveryagent Test:
#####################################################################
# Collect test data
file(GLOB_RECURSE test_data_glob
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
data/*)
list(APPEND test_data ${test_data_glob})
qt_internal_add_test(tst_qquickdeliveryagent
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_qquickdeliveryagent.cpp
DEFINES
QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
INCLUDE_DIRECTORIES
../../shared
PUBLIC_LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
Qt::QmlPrivate
Qt::QuickPrivate
TESTDATA ${test_data}
)
## Scopes:
#####################################################################
qt_internal_extend_target(tst_qquickdeliveryagent CONDITION ANDROID OR IOS
DEFINES
QT_QMLTEST_DATADIR=\\\":/data\\\"
)
qt_internal_extend_target(tst_qquickdeliveryagent CONDITION NOT ANDROID AND NOT IOS
DEFINES
QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
)

View File

@ -0,0 +1,14 @@
import QtQuick
Rectangle {
id: root
objectName: "root"
color: th.pressed ? "goldenrod" : "khaki"
border.color: "black"
width: 100; height: 100
TapHandler {
id: th
objectName: root.objectName + "Tap"
onTapped: console.log(this, objectName)
}
}

View File

@ -0,0 +1,186 @@
/****************************************************************************
**
** Copyright (C) 2021 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$
**
****************************************************************************/
#include <qtest.h>
#include <QSignalSpy>
#include <QDebug>
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlComponent>
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickView>
#include <QtQuick/QQuickWindow>
#include <QtQuick/private/qquickshadereffectsource_p.h>
#include <QtQuick/private/qquicktaphandler_p.h>
#include <QtQuick/private/qquickwindow_p.h>
#include "../../shared/util.h"
#include "../shared/visualtestutil.h"
#include "../shared/viewtestutil.h"
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
struct ViewportTransformHelper : public QQuickDeliveryAgent::Transform
{
QPointF offset = QPointF(50, 50);
// Transforms window coordinates to subscene coordinates.
QPointF map(const QPointF &viewportPoint) override {
qCDebug(lcTests) << viewportPoint << "->" << viewportPoint - offset;
return viewportPoint - offset;
}
};
// A QQuick3DViewport simulator
class SubsceneRootItem : public QQuickShaderEffectSource
{
public:
SubsceneRootItem(QQuickItem *source, QRectF bounds, QQuickItem *parent = nullptr)
: QQuickShaderEffectSource(parent),
deliveryAgent(QQuickItemPrivate::get(source)->ensureSubsceneDeliveryAgent())
{
setAcceptedMouseButtons(Qt::AllButtons);
setAcceptTouchEvents(true);
setAcceptHoverEvents(true);
setSourceItem(source);
setSize(bounds.size());
setPosition(bounds.topLeft());
setOpacity(0.5);
deliveryAgent->setObjectName("subscene");
}
QQuickDeliveryAgent *deliveryAgent = nullptr;
protected:
bool event(QEvent *e) override {
if (e->isPointerEvent()) {
bool ret = false;
auto pe = static_cast<QPointerEvent *>(e);
QVarLengthArray<QPointF, 16> originalScenePositions;
originalScenePositions.resize(pe->pointCount());
for (int pointIndex = 0; pointIndex < pe->pointCount(); ++pointIndex)
originalScenePositions[pointIndex] = pe->point(pointIndex).scenePosition();
for (int pointIndex = 0; pointIndex < pe->pointCount(); ++pointIndex) {
QMutableEventPoint &mut = QMutableEventPoint::from(pe->point(pointIndex));
mut.setScenePosition(vxh->map(mut.scenePosition()));
mut.setPosition(mut.position());
}
qCDebug(lcTests) << "forwarding to subscene DA" << pe;
if (deliveryAgent->event(pe)) {
ret = true;
if (QQuickDeliveryAgentPrivate::anyPointGrabbed(pe))
deliveryAgent->setSceneTransform(vxh); // takes ownership
}
// restore original scene positions
for (int pointIndex = 0; pointIndex < pe->pointCount(); ++pointIndex)
QMutableEventPoint::from(pe->point(pointIndex)).setScenePosition(originalScenePositions.at(pointIndex));
pe->setAccepted(false); // reject implicit grab and let it keep propagating
qCDebug(lcTests) << e << "returning" << ret;
return ret;
} else {
return QQuickShaderEffectSource::event(e);
}
}
ViewportTransformHelper *vxh = new ViewportTransformHelper;
};
class tst_qquickdeliveryagent : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquickdeliveryagent()
{
}
private slots:
void passiveGrabberOrder();
private:
QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice());
};
void tst_qquickdeliveryagent::passiveGrabberOrder()
{
QQuickView view;
QQmlComponent component(view.engine());
component.loadUrl(testFileUrl("tapHandler.qml"));
view.setContent(QUrl(), &component, component.create());
view.resize(160, 160);
QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject());
QVERIFY(root);
QQuickTapHandler *rootTap = root->findChild<QQuickTapHandler *>();
QVERIFY(rootTap);
QScopedPointer<QQuickItem> subsceneRect(qobject_cast<QQuickItem *>(component.createWithInitialProperties({{"objectName", "child"}})));
QVERIFY(subsceneRect);
QQuickTapHandler *subsceneTap = subsceneRect->findChild<QQuickTapHandler *>();
QVERIFY(subsceneTap);
SubsceneRootItem subscene(subsceneRect.data(), {50, 50, 100, 100}, view.rootObject());
QCOMPARE(subsceneRect->parentItem(), nullptr);
QQuickDeliveryAgent *windowAgent = QQuickWindowPrivate::get(&view)->deliveryAgent;
windowAgent->setObjectName("window");
QVERIFY(subscene.deliveryAgent);
QVERIFY(subscene.deliveryAgent != windowAgent);
QQuickVisualTestUtil::SignalMultiSpy spy;
QVERIFY(spy.connectToSignal(rootTap, &QQuickTapHandler::tapped));
QVERIFY(spy.connectToSignal(subsceneTap, &QQuickTapHandler::tapped));
view.show();
QVERIFY(QTest::qWaitForWindowActive(&view));
QPoint pos(75, 75);
QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, pos);
QTest::qWait(1000);
auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice());
const auto &persistentPoint = devPriv->activePoints.values().first();
qCDebug(lcTests) << "passive grabbers" << persistentPoint.passiveGrabbers << "contexts" << persistentPoint.passiveGrabbersContext;
QCOMPARE(persistentPoint.passiveGrabbers.count(), 2);
QCOMPARE(persistentPoint.passiveGrabbers.first(), subsceneTap);
QCOMPARE(persistentPoint.passiveGrabbersContext.first(), subscene.deliveryAgent);
QCOMPARE(persistentPoint.passiveGrabbers.last(), rootTap);
QTest::mouseRelease(&view, Qt::LeftButton);
QTest::qWait(100);
// QQuickWindow::event() has failsafe: clear all grabbers after release
QCOMPARE(persistentPoint.passiveGrabbers.count(), 0);
qCDebug(lcTests) << "TapHandlers emitted tapped in this order:" << spy.senders;
QCOMPARE(spy.senders.count(), 2);
// passive grabbers are visited in order, and emit tapped() at that time
QCOMPARE(spy.senders.first(), subsceneTap);
QCOMPARE(spy.senders.last(), rootTap);
}
QTEST_MAIN(tst_qquickdeliveryagent)
#include "tst_qquickdeliveryagent.moc"

View File

@ -98,6 +98,33 @@ namespace QQuickVisualTestUtil
}
bool compareImages(const QImage &ia, const QImage &ib, QString *errorMessage);
struct SignalMultiSpy : public QObject
{
Q_OBJECT
public:
QList<QObject *> senders;
QList<QByteArray> signalNames;
template <typename Func1>
QMetaObject::Connection connectToSignal(const typename QtPrivate::FunctionPointer<Func1>::Object *obj, Func1 signal,
Qt::ConnectionType type = Qt::AutoConnection)
{
return connect(obj, signal, this, &SignalMultiSpy::receive, type);
}
void clear() {
senders.clear();
signalNames.clear();
}
public slots:
void receive() {
QMetaMethod m = sender()->metaObject()->method(senderSignalIndex());
senders << sender();
signalNames << m.name();
}
};
}
#endif // QQUICKVISUALTESTUTIL_H