fix(input): optimize keyboard height/tilt, isolate text fields, and fix transport dock trigger

This commit is contained in:
Pixelplanet committed 2026-10-20 15:06:50 +02:00
1 parent f26405797e
commit ec9821f1af
4 files changed
+80 -46

No files matched your search

+24 -3
View File
@@ -313,6 +313,13 @@ void App::loadMedia(const std::string& path) {
}
hudDismissed_ = false;
lastControllerActivity_ = std::chrono::steady_clock::now();
VrKeyboard::instance().hide();
actionMenu_.visible = false;
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
browser_.setOpen(false);
downloadsWindow_ = false;
pendingDl_.clear();
// Refine the stereo layout from the frame aspect ratio when the filename did
// not state it: once the projection is a VR type the aspect is unambiguous
@@ -665,6 +672,8 @@ void App::saveSmbModal() {
SmbCredentialsManager::instance().addOrUpdateShare(s);
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
VrKeyboard::instance().hide();
actionMenu_.visible = false;
browser_.openNetwork();
#endif
@@ -676,6 +685,8 @@ void App::deleteSmbModal() {
SmbCredentialsManager::instance().deleteShare(smbModal_.origHost, smbModal_.origShare);
}
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
VrKeyboard::instance().hide();
actionMenu_.visible = false;
browser_.openNetwork();
#endif
@@ -1115,12 +1126,18 @@ int App::run() {
if (in.browserToggle) {
if (actionMenu_.visible) {
actionMenu_.visible = false;
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
VrKeyboard::instance().hide();
} else if (downloadsWindow_) {
downloadsWindow_ = false;
} else if (settings_.isOpen()) {
settings_.close();
} else if (browser_.isOpen()) {
browser_.setOpen(false);
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
VrKeyboard::instance().hide();
} else if (mediaOpen_ && renderer_.controlBarVisible()) {
// HUD is visible during playback: B/Y dismisses the HUD!
dismissHud(in, nowT);
@@ -1544,11 +1561,11 @@ int App::run() {
}
}
const bool uiPanelActive = browserOpen || settingsOpen ||
downloadsWindow_ || !pendingDl_.empty() || actionMenu_.visible;
downloadsWindow_ || !pendingDl_.empty() || actionMenu_.visible || smbModal_.active;
// The Transport HUD is only visible during playback when the
// Unified Spatial Hub and modals are closed.
const bool barBlocked = browserOpen || settingsOpen || downloadsWindow_ ||
!pendingDl_.empty() || actionMenu_.visible;
!pendingDl_.empty() || actionMenu_.visible || smbModal_.active;
// Controls (buttons + timeline) show only while a controller is
// actively moving; they fade after a few seconds of stillness.
renderer_.setControlBar(!barBlocked && controllersEngaged && !hudDismissed_ &&
@@ -1932,6 +1949,8 @@ int App::run() {
// Cancel
actionMenu_.visible = false;
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
VrKeyboard::instance().hide();
} else {
// Delete Share
deleteSmbModal();
@@ -1940,6 +1959,8 @@ int App::run() {
// Cancel
actionMenu_.visible = false;
smbModal_.active = false;
smbModal_.editingField = SmbModalState::Field::None;
VrKeyboard::instance().hide();
}
}
interacted = true;
@@ -2209,7 +2230,7 @@ int App::run() {
interacted = true;
}
// Click outside the control dock in empty space immediately hides the HUD
if (in.select && btn < 0 && !renderer_.pointerOnTimeline() && !renderer_.pointerOnTimeToggle()) {
if (in.select && !renderer_.pointerActive()) {
dismissHud(in, nowT);
interacted = true;
}
+14 -13
View File
@@ -1312,16 +1312,7 @@ void VulkanRenderer::updatePointer() {
// 0. Virtual Keyboard takes top focus priority
if (kbOpen) {
// First check if pointing at the text preview box on the panel
if (hx >= -kPanelW && hx <= kPanelW && hyy >= -kPanelTop && hyy <= kPanelTop) {
pointerActive_ = true;
pointerHand_ = h;
pointerHit_[0] = world.x;
pointerHit_[1] = world.y;
pointerHit_[2] = world.z;
break;
}
// Next check if pointing directly at the virtual keyboard overlay keys
// First priority: Check if pointing directly at the virtual keyboard overlay keys
if (keyboardModal_.hasTransform) {
const auto& m = keyboardModal_.transform;
// Column 0: Local X axis (right)
@@ -1341,9 +1332,9 @@ void VulkanRenderer::updatePointer() {
const Vec3 rel = hitP - kbCenter;
const float localX = dot(rel, kbRight);
const float localY = dot(rel, kbUp);
// Overlay width is 1.05m (halfW = 0.525m). Height is ~0.45m (halfH = 0.225m).
// Allow generous interactive margin (0.58m x 0.28m)
if (std::fabs(localX) <= 0.58f && std::fabs(localY) <= 0.28f) {
// Overlay width is 1.05m (halfW = 0.525m). Height is ~0.40m (halfH = 0.20m).
// Allow generous interactive margin (0.58m x 0.26m)
if (std::fabs(localX) <= 0.58f && std::fabs(localY) <= 0.26f) {
pointerActive_ = true;
pointerHand_ = h;
pointerHit_[0] = hitP.x;
@@ -1354,6 +1345,16 @@ void VulkanRenderer::updatePointer() {
}
}
}
// Second priority: Check if pointing at the floating modal text box directly above the keyboard
// Modal box center is cy = 0.16f, half-width 0.23f, half-height 0.13f
if (std::fabs(hx) <= 0.25f && std::fabs(hyy - 0.16f) <= 0.14f) {
pointerActive_ = true;
pointerHand_ = h;
pointerHit_[0] = world.x;
pointerHit_[1] = world.y;
pointerHit_[2] = world.z;
break;
}
continue;
}
+41 -30
View File
@@ -212,11 +212,18 @@ bool VrKeyboard::show(const std::string& description,
auto* ov = reinterpret_cast<vr::IVROverlay*>(vrOverlay_);
if (!ov) return false;
if (isOpen_) {
hide();
}
// Explicitly hide any existing keyboard session in SteamVR to clear lingering text buffers
ov->HideKeyboard();
description_ = description;
existingText_ = existingText;
currentText_ = existingText;
isPassword_ = isPassword;
doneSubmitted_ = false;
userTyped_ = false;
const vr::EGamepadTextInputMode mode = isPassword
? vr::k_EGamepadTextInputModePassword
@@ -257,15 +264,18 @@ bool VrKeyboard::show(const std::string& description,
}
const float yaw = std::atan2(-fx, -fz);
// Place keyboard 1.05m in front of eyes, 0.35m below eye level
// Place keyboard 1.05m in front of eyes, 0.20m below eye level.
// The modal dialog box above ends at ~hy + 0.03m; this places the top edge of the
// keyboard (half-height ~0.20m) right at hy - 0.004m, exactly 3.4cm below the modal dialog.
const float dist = 1.05f;
const float kbX = hx + fx * dist;
const float kbY = hy - 0.35f;
const float kbY = hy - 0.20f;
const float kbZ = hz + fz * dist;
// Tilt upward toward eyes by ~22 degrees (-0.38 rad).
// Top side slants backward away from user; face normal points up and toward eyes.
const float tilt = -0.38f;
// Tilt upward toward eyes by ~11.5 degrees (-0.20 rad).
// Center is 20cm below eyes at 1.05m distance: atan2(0.20, 1.05) ~= 0.189 rad.
// Tilt of -0.20 rad angles the keyboard face normal directly at the user's eyes!
const float tilt = -0.20f;
vr::HmdMatrix34_t kbMat{};
kbMat.m[0][0] = std::cos(yaw);
kbMat.m[0][1] = std::sin(yaw) * std::sin(tilt);
@@ -329,6 +339,9 @@ void VrKeyboard::hide() {
isOpen_ = false;
kbConfigured_ = false;
doneSubmitted_ = false;
userTyped_ = false;
currentText_.clear();
existingText_.clear();
PIXELVR_LOG_INFO("VrKeyboard: Hidden virtual keyboard");
}
@@ -342,6 +355,7 @@ void VrKeyboard::poll(std::function<void(const std::string&)> onDone,
vr::VREvent_t event{};
while (sys->PollNextEvent(&event, sizeof(event))) {
if (event.eventType == vr::VREvent_KeyboardCharInput) {
userTyped_ = true;
char keyBuf[1024] = {0};
uint32_t len = ov->GetKeyboardText(keyBuf, sizeof(keyBuf));
if (len > 0) {
@@ -374,6 +388,10 @@ void VrKeyboard::poll(std::function<void(const std::string&)> onDone,
if (len > 0) {
text = std::string(buf, len < sizeof(buf) ? len : std::strlen(buf));
}
ov->HideKeyboard();
currentText_.clear();
existingText_.clear();
userTyped_ = false;
PIXELVR_LOG_INFO("VrKeyboard: User submitted text via KeyboardDone: '%s'", text.c_str());
if (onDone) {
onDone(text);
@@ -382,27 +400,16 @@ void VrKeyboard::poll(std::function<void(const std::string&)> onDone,
event.eventType == vr::VREvent_KeyboardClosed_Global) {
isOpen_ = false;
kbConfigured_ = false;
ov->HideKeyboard();
PIXELVR_LOG_INFO("VrKeyboard: Keyboard closed (doneSubmitted=%d)", doneSubmitted_ ? 1 : 0);
if (!doneSubmitted_) {
// On Steam Frame / SteamOS, pressing the checkmark or closing the keyboard issues KeyboardClosed
// without KeyboardDone. Read the buffer and treat non-empty / modified input as submit!
char buf[1024] = {0};
uint32_t len = ov->GetKeyboardText(buf, sizeof(buf));
std::string text = currentText_;
if (len > 0) {
text = std::string(buf, len < sizeof(buf) ? len : std::strlen(buf));
}
if (!text.empty() || text != existingText_) {
PIXELVR_LOG_INFO("VrKeyboard: Treating KeyboardClosed as submit with text: '%s'", text.c_str());
doneSubmitted_ = true;
if (onDone) {
onDone(text);
}
} else {
PIXELVR_LOG_INFO("VrKeyboard: Keyboard cancelled without text change");
if (onCancel) {
onCancel();
}
// Closed without KeyboardDone: treat as cancel and forget any input
currentText_.clear();
existingText_.clear();
userTyped_ = false;
PIXELVR_LOG_INFO("VrKeyboard: Discarded input on KeyboardClosed");
if (onCancel) {
onCancel();
}
}
}
@@ -412,15 +419,19 @@ void VrKeyboard::poll(std::function<void(const std::string&)> onDone,
if (isOpen_) {
pollFramesSinceOpen_++;
// Maintain and reinforce keyboard overlay configuration across the first 90 frames (~1 sec)
// to catch asynchronous creation by SteamVR's GamepadUI
if (!kbConfigured_ || pollFramesSinceOpen_ < 90) {
// until successfully configured
if (!kbConfigured_ && pollFramesSinceOpen_ < 90) {
configureKeyboardOverlay();
}
char liveBuf[1024] = {0};
uint32_t liveLen = ov->GetKeyboardText(liveBuf, sizeof(liveBuf));
if (liveLen > 0) {
currentText_ = std::string(liveBuf, liveLen < sizeof(liveBuf) ? liveLen : std::strlen(liveBuf));
// Only synchronize with SteamVR live buffer if the user has actively typed in this session.
// This avoids contaminating a fresh/empty input field with stale residual buffer from a previous field.
if (userTyped_) {
char liveBuf[1024] = {0};
uint32_t liveLen = ov->GetKeyboardText(liveBuf, sizeof(liveBuf));
if (liveLen > 0) {
currentText_ = std::string(liveBuf, liveLen < sizeof(liveBuf) ? liveLen : std::strlen(liveBuf));
}
}
}
}
+1
View File
@@ -48,6 +48,7 @@ private:
bool available_ = false;
bool isOpen_ = false;
bool doneSubmitted_ = false;
bool userTyped_ = false;
bool kbConfigured_ = false;
int pollFramesSinceOpen_ = 0;
float kbMat_[3][4]{};