#include #include #include #include #include #include #include #include "input/OverlayInputBridge.h" // Feeds synthetic SteamVR overlay events into OverlayInputBridge and checks // that the QML scene reacts. No graphics are initialized; event delivery // doesn't need them. class InputBridgeTest : public QObject { Q_OBJECT private slots: void init() { m_renderControl = std::make_unique(); m_window = std::make_unique(m_renderControl.get()); m_window->setGeometry(0, 0, kSize.width(), kSize.height()); QQmlComponent component(&m_engine); component.setData(R"( import QtQuick Item { width: 400; height: 300 property int clicks: 0 MouseArea { objectName: "top"; width: 400; height: 100 onClicked: parent.clicks++ } Flickable { objectName: "flick"; y: 100; width: 400; height: 100 contentHeight: 1000 } TextInput { objectName: "input"; y: 250; width: 400; height: 50 } })", QUrl()); m_root.reset(qobject_cast(component.create())); QVERIFY2(m_root, qPrintable(component.errorString())); m_root->setParentItem(m_window->contentItem()); QFocusEvent focusIn(QEvent::FocusIn, Qt::ActiveWindowFocusReason); QCoreApplication::sendEvent(m_window.get(), &focusIn); m_bridge = std::make_unique(m_window.get(), kSize, kOverlay); } void cleanup() { m_bridge.reset(); m_root.reset(); m_window.reset(); m_renderControl.reset(); } void clickUsesGlCoordinates() { // GL y=250 (from bottom) is Qt y=50: inside the top MouseArea. click(200, 250); QCOMPARE(m_root->property("clicks").toInt(), 1); // GL y=50 is Qt y=250: below it, must not count. click(200, 50); QCOMPARE(m_root->property("clicks").toInt(), 1); } void smoothScrollMovesFlickable() { auto* flick = m_root->findChild("flick"); mouse(vr::VREvent_MouseMove, 200, kSize.height() - 150); for (int i = 0; i < 5; ++i) send(scrollEvent(0.f, -1.f)); QTRY_VERIFY(flick->property("contentY").toReal() > 0); } void keyboardCharsReachFocusedTextInput() { auto* input = m_root->findChild("input"); input->forceActiveFocus(); QVERIFY(input->hasActiveFocus()); send(keyboardEvent("ab", kOverlay)); send(keyboardEvent("\xc3\xa4", kOverlay)); // UTF-8 'รค' send(keyboardEvent("\b", kOverlay)); // Other overlay: ignored QVERIFY(!m_bridge->handleEvent(keyboardEvent("c", kOverlay + 1))); QCOMPARE(input->property("text").toString(), QStringLiteral("ab")); } private: static constexpr QSize kSize{400, 300}; static constexpr vr::VROverlayHandle_t kOverlay = 42; void send(const vr::VREvent_t& event) { QVERIFY(m_bridge->handleEvent(event)); } void mouse(uint32_t type, float x, float y) { vr::VREvent_t event{}; event.eventType = type; event.data.mouse.x = x; event.data.mouse.y = y; event.data.mouse.button = vr::VRMouseButton_Left; send(event); } void click(float x, float y) { mouse(vr::VREvent_MouseMove, x, y); mouse(vr::VREvent_MouseButtonDown, x, y); mouse(vr::VREvent_MouseButtonUp, x, y); } static vr::VREvent_t scrollEvent(float dx, float dy) { vr::VREvent_t event{}; event.eventType = vr::VREvent_ScrollSmooth; event.data.scroll.xdelta = dx; event.data.scroll.ydelta = dy; return event; } static vr::VREvent_t keyboardEvent(const char* utf8, vr::VROverlayHandle_t overlay) { vr::VREvent_t event{}; event.eventType = vr::VREvent_KeyboardCharInput; qstrncpy(event.data.keyboard.cNewInput, utf8, sizeof(event.data.keyboard.cNewInput)); event.data.keyboard.overlayHandle = overlay; return event; } QQmlEngine m_engine; std::unique_ptr m_renderControl; std::unique_ptr m_window; std::unique_ptr m_root; std::unique_ptr m_bridge; }; QTEST_MAIN(InputBridgeTest) #include "tst_inputbridge.moc"