Initial FrameAssistant baseline from the vrdashboard template
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>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
#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"
|
||||
Reference in New Issue
Block a user