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:
parent
1dc4bdbabf
commit
152e12dc22
|
@ -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)
|
||||
|
|
|
@ -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\\\"
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue