Template stripped to the overlay platform (examples, demo UI and Frame test package removed), renamed to FrameAssistant, and working rules for the project recorded in CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
4.3 KiB
C++
138 lines
4.3 KiB
C++
#include <QFocusEvent>
|
|
#include <QQmlComponent>
|
|
#include <QQmlEngine>
|
|
#include <QQuickItem>
|
|
#include <QQuickRenderControl>
|
|
#include <QQuickWindow>
|
|
#include <QtTest>
|
|
|
|
#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<QQuickRenderControl>();
|
|
m_window = std::make_unique<QQuickWindow>(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<QQuickItem*>(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<OverlayInputBridge>(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<QQuickItem*>("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<QQuickItem*>("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<QQuickRenderControl> m_renderControl;
|
|
std::unique_ptr<QQuickWindow> m_window;
|
|
std::unique_ptr<QQuickItem> m_root;
|
|
std::unique_ptr<OverlayInputBridge> m_bridge;
|
|
};
|
|
|
|
QTEST_MAIN(InputBridgeTest)
|
|
#include "tst_inputbridge.moc"
|