mirror of
https://github.com/Pixelplanet/Pixel-VR-Player.git
synced 2026-10-20 16:00:52 +02:00
fix(ui): fix HUD dismissal flicker and center transport recessed island alignment
- Prevent in.selectHeld and trigger activity from resetting hudDismissed_ after manual dismiss - Add App::dismissHud() to reset activity anchors to current controller poses and establish a 500ms cooldown - Center transport recessed island background at cx=0.0f, hw=0.34f, giving symmetrical 34mm padding across all 5 buttons - Add hover tooltip pills for [Hide UI] and [Stop]
This commit is contained in:
1 parent
7aede92244
commit
2f684e23a2
4 files changed
+77
-21
No files matched your search
@@ -53,6 +53,19 @@
|
||||
- Updated `tools/package-release.sh` to package updater scripts in the release tarball.
|
||||
- Updated `README.md` with comprehensive update instructions across all platforms.
|
||||
|
||||
- [x] **6. Fix UI Dismissal Flicker and Recessed Island Alignment**
|
||||
- **Symptom:** Clicking outside the controls or clicking the Hide UI button caused the UI to briefly flicker for 1 frame and immediately reappear. The Hide UI button was also visually misaligned (half outside the dark island).
|
||||
- **Root Cause:**
|
||||
- `in.selectHeld` and trigger activity handling in `App::tick()` continuously reset `hudDismissed_ = false` and set `activity = true` on subsequent frames of the trigger pull, fighting the dismissal call.
|
||||
- Controller activity anchors were not reset on dismissal, and there was no dismissal grace cooldown, causing slight hand tremor during trigger release to immediately re-trigger movement thresholds.
|
||||
- The center transport recessed island background in `VulkanRenderer` was rendered at `cx = -0.05f, hw = 0.30f`, clipping through the center of the Hide UI button at `+0.26f`.
|
||||
- **Resolution:**
|
||||
- Created `App::dismissHud()` which sets `hudDismissed_ = true`, records `hudDismissTime_`, zeroes out activity, hides the control bar and laser ray, and snaps controller activity anchors to their exact current positions.
|
||||
- Added a 500ms dismissal cooldown preventing accidental re-engagement from trigger release tremors.
|
||||
- Blocked `in.select` and `in.selectHeld` from clearing `hudDismissed_` while the HUD is dismissed.
|
||||
- Re-centered the transport recessed island background to `cx = 0.0f, hw = 0.34f`, creating perfect 34mm symmetrical padding for all 5 buttons (`Stop`, `SeekBack`, `PlayPause`, `SeekFwd`, `HideHud`).
|
||||
- Added hover tooltip pills for `[Hide UI]` and `[Stop]`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
+36
-19
@@ -311,6 +311,8 @@ void App::loadMedia(const std::string& path) {
|
||||
PIXELVR_LOG_WARN("Failed to open %s", path.c_str());
|
||||
return;
|
||||
}
|
||||
hudDismissed_ = false;
|
||||
lastControllerActivity_ = std::chrono::steady_clock::now();
|
||||
|
||||
// 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
|
||||
@@ -548,6 +550,20 @@ void App::performVaultLock() {
|
||||
PIXELVR_LOG_INFO("Vault locked and sanitized");
|
||||
}
|
||||
|
||||
void App::dismissHud(const XrInputState& in, std::chrono::steady_clock::time_point nowT) {
|
||||
hudDismissed_ = true;
|
||||
hudDismissTime_ = nowT;
|
||||
lastControllerActivity_ = nowT - std::chrono::seconds(10);
|
||||
renderer_.setControlBar(false);
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
if (in.controllers[h].active) {
|
||||
for (int k = 0; k < 3; ++k) activityAnchorPos_[h][k] = in.controllers[h].position[k];
|
||||
for (int k = 0; k < 4; ++k) activityAnchorOrient_[h][k] = in.controllers[h].orientation[k];
|
||||
}
|
||||
}
|
||||
PIXELVR_LOG_INFO("Player HUD dismissed");
|
||||
}
|
||||
|
||||
void App::openSmbModal(bool isNew, const std::string& smbPath) {
|
||||
#ifdef PIXELVR_HAVE_FREETYPE
|
||||
smbModal_.active = true;
|
||||
@@ -1018,6 +1034,8 @@ int App::run() {
|
||||
// Controller activity: moving a controller past a small threshold (or
|
||||
// any button/stick input) keeps the laser, models and controls shown;
|
||||
// after a few seconds of stillness they fade away.
|
||||
const auto timeSinceDismiss = nowT - hudDismissTime_;
|
||||
const bool inDismissCooldown = hudDismissed_ && (timeSinceDismiss < std::chrono::milliseconds(500));
|
||||
bool activity = false;
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
const auto& c = in.controllers[h];
|
||||
@@ -1042,8 +1060,8 @@ int App::run() {
|
||||
const float rotDiff = std::fabs(od);
|
||||
|
||||
if (hudDismissed_) {
|
||||
// Requires intentional hand movement to re-engage HUD after manual dismissal
|
||||
if (moveDist > 0.10f || rotDiff < 0.970f) {
|
||||
// Requires intentional hand movement after dismissal cooldown to re-engage HUD
|
||||
if (!inDismissCooldown && (moveDist > 0.12f || rotDiff < 0.950f)) {
|
||||
hudDismissed_ = false;
|
||||
activity = true;
|
||||
for (int k = 0; k < 3; ++k) activityAnchorPos_[h][k] = c.position[k];
|
||||
@@ -1056,12 +1074,17 @@ int App::run() {
|
||||
for (int k = 0; k < 4; ++k) activityAnchorOrient_[h][k] = c.orientation[k];
|
||||
}
|
||||
}
|
||||
if (in.togglePlay || in.browserToggle || in.select || in.selectHeld ||
|
||||
in.recenter || std::fabs(in.thumbstickX) > 0.15f ||
|
||||
std::fabs(in.thumbstickY) > 0.15f ||
|
||||
std::fabs(in.thumbstickRX) > 0.15f) {
|
||||
const bool stickMoved = (std::fabs(in.thumbstickX) > 0.25f ||
|
||||
std::fabs(in.thumbstickY) > 0.25f ||
|
||||
std::fabs(in.thumbstickRX) > 0.25f ||
|
||||
std::fabs(in.thumbstickRY) > 0.25f);
|
||||
if (in.togglePlay || in.browserToggle || in.recenter || stickMoved) {
|
||||
activity = true;
|
||||
if (!inDismissCooldown) {
|
||||
hudDismissed_ = false;
|
||||
}
|
||||
} else if (!hudDismissed_ && (in.select || in.selectHeld)) {
|
||||
activity = true;
|
||||
hudDismissed_ = false;
|
||||
}
|
||||
if (activity) {
|
||||
lastControllerActivity_ = nowT;
|
||||
@@ -1085,9 +1108,7 @@ int App::run() {
|
||||
browser_.setOpen(false);
|
||||
} else if (mediaOpen_ && renderer_.controlBarVisible()) {
|
||||
// HUD is visible during playback: B/Y dismisses the HUD!
|
||||
hudDismissed_ = true;
|
||||
lastControllerActivity_ = nowT - std::chrono::seconds(10);
|
||||
renderer_.setControlBar(false);
|
||||
dismissHud(in, nowT);
|
||||
} else {
|
||||
browser_.open();
|
||||
}
|
||||
@@ -1523,7 +1544,7 @@ int App::run() {
|
||||
if (kbActive && !renderer_.laserOn(0) && !renderer_.laserOn(1)) {
|
||||
renderer_.setLaserOn(1, true);
|
||||
}
|
||||
const bool showLaser = (uiPanelActive || controllersEngaged || kbActive);
|
||||
const bool showLaser = (uiPanelActive || (controllersEngaged && !hudDismissed_) || kbActive);
|
||||
// Build each controller's render state: corrected model pose,
|
||||
// low-pass smoothed (the raw hand pose jitters), while the aim
|
||||
// pose stays raw so the laser remains responsive.
|
||||
@@ -2067,8 +2088,8 @@ int App::run() {
|
||||
|
||||
// Cinema Transport Dock is only active when the Unified Spatial Hub is closed.
|
||||
const bool barOk = !browserOpen && !settingsOpen && !downloadsWindow_ &&
|
||||
pendingDl_.empty();
|
||||
if (barOk) {
|
||||
pendingDl_.empty() && !actionMenu_.visible && !smbModal_.active;
|
||||
if (barOk && renderer_.controlBarVisible()) {
|
||||
// Point-and-click the media control bar with the trigger.
|
||||
const int btn = renderer_.pointerButton();
|
||||
// The trigger clicks the bar; A does too (A is the reliable
|
||||
@@ -2160,18 +2181,14 @@ int App::run() {
|
||||
renderer_.setSphereZoom(1.0f);
|
||||
}
|
||||
} else if (cb == VulkanRenderer::CtrlButton::HideHud) {
|
||||
hudDismissed_ = true;
|
||||
lastControllerActivity_ = nowT - std::chrono::seconds(10);
|
||||
renderer_.setControlBar(false);
|
||||
dismissHud(in, nowT);
|
||||
}
|
||||
#endif
|
||||
interacted = true;
|
||||
}
|
||||
// Click outside the control dock in empty space immediately hides the HUD
|
||||
if (in.select && btn < 0 && !renderer_.pointerOnTimeline() && !renderer_.pointerOnTimeToggle()) {
|
||||
hudDismissed_ = true;
|
||||
lastControllerActivity_ = nowT - std::chrono::seconds(10);
|
||||
renderer_.setControlBar(false);
|
||||
dismissHud(in, nowT);
|
||||
interacted = true;
|
||||
}
|
||||
if (in.select && renderer_.pointerOnTimeToggle()) {
|
||||
|
||||
@@ -78,6 +78,8 @@ private:
|
||||
bool downloadsWindow_ = false; // downloads list window toggled from the bar
|
||||
bool dlWasActive_ = false; // edge detect: auto-open the window on start
|
||||
bool hudDismissed_ = false; // manually dismissed player controls / HUD
|
||||
std::chrono::steady_clock::time_point hudDismissTime_{}; // timestamp of manual dismissal
|
||||
void dismissHud(const XrInputState& in, std::chrono::steady_clock::time_point nowT);
|
||||
// Low-pass smoothed model pose per hand (the raw hand pose jitters; the
|
||||
// system's own controller model is visibly smoothed).
|
||||
bool modelSmoothInit_[2] = {false, false};
|
||||
|
||||
@@ -2252,8 +2252,10 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
drawUiElement(vpPanel, 0.0f, kBarY + 0.125f, kDockHalfW - 0.045f, 0.052f,
|
||||
UiShape::RoundedBox, grooveBg, 0.35f, 0.015f, grooveBorder);
|
||||
|
||||
// Center transport recessed island
|
||||
drawUiElement(vpPanel, -0.05f, kBarY - 0.045f, 0.30f, 0.098f,
|
||||
// Center transport recessed island: centered at 0.0f, symmetrically
|
||||
// enclosing [Stop] (-0.26), [Seek -10] (-0.13), (Play/Pause) (0.00),
|
||||
// [Seek +10] (+0.13), and [Hide UI] (+0.26).
|
||||
drawUiElement(vpPanel, 0.0f, kBarY - 0.045f, 0.34f, 0.098f,
|
||||
UiShape::RoundedBox, grooveBg, 0.50f, 0.015f, grooveBorder);
|
||||
|
||||
const float btnBgHover[4] = {
|
||||
@@ -2364,6 +2366,19 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
UiShape::RoundedBox, tipBg, 0.35f, 0.03f, themeAccentCol);
|
||||
}
|
||||
|
||||
// Button hover tooltip pills
|
||||
if (pointerButton_ == static_cast<int>(CtrlButton::HideHud)) {
|
||||
const CtrlButtonRect rH = get_ctrl_button_rect(static_cast<int>(CtrlButton::HideHud));
|
||||
const float tipBg[4] = {0.04f, 0.07f, 0.12f, 0.96f};
|
||||
drawUiElement(vpPanel, rH.cx, rH.cy + 0.065f, 0.068f, 0.024f,
|
||||
UiShape::RoundedBox, tipBg, 0.35f, 0.025f, themeAccentCol);
|
||||
} else if (pointerButton_ == static_cast<int>(CtrlButton::Stop)) {
|
||||
const CtrlButtonRect rSt = get_ctrl_button_rect(static_cast<int>(CtrlButton::Stop));
|
||||
const float tipBg[4] = {0.04f, 0.07f, 0.12f, 0.96f};
|
||||
drawUiElement(vpPanel, rSt.cx, rSt.cy + 0.065f, 0.052f, 0.024f,
|
||||
UiShape::RoundedBox, tipBg, 0.35f, 0.025f, themeAccentCol);
|
||||
}
|
||||
|
||||
if (textReady_) {
|
||||
vkCmdBindPipeline(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, textPipeline_);
|
||||
vkCmdBindDescriptorSets(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
@@ -2407,6 +2422,15 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
|
||||
const CtrlButtonRect rS = get_ctrl_button_rect(static_cast<int>(CtrlButton::ScreenToggle));
|
||||
drawFitCentered(rS.cx, rS.cy, rS.hw * 1.88f, pillSc, themeTextCol, hudScreen_);
|
||||
|
||||
// Tooltip text for button hover
|
||||
if (pointerButton_ == static_cast<int>(CtrlButton::HideHud)) {
|
||||
const CtrlButtonRect rH = get_ctrl_button_rect(static_cast<int>(CtrlButton::HideHud));
|
||||
drawFitCentered(rH.cx, rH.cy + 0.065f, 0.12f, pillSc * 0.90f, themeTextCol, "Hide UI");
|
||||
} else if (pointerButton_ == static_cast<int>(CtrlButton::Stop)) {
|
||||
const CtrlButtonRect rSt = get_ctrl_button_rect(static_cast<int>(CtrlButton::Stop));
|
||||
drawFitCentered(rSt.cx, rSt.cy + 0.065f, 0.09f, pillSc * 0.90f, themeTextCol, "Stop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user