mirror of
https://github.com/Pixelplanet/Pixel-VR-Player.git
synced 2026-10-20 11:00:44 +02:00
feat(input): trigger selects in the browser, grip toggles the laser, downloads window replaces the browser
- Restore trigger-to-select in the file browser (it is the intuitive gesture); A remains an alternative for thumbstick/d-pad navigation. Both skip when the laser is resting on a control-bar button, which owns that click. - Grip now toggles that hand's laser pointer, SteamVR-style: turn one off to drive a panel with the stick without the beam fighting it, and to pick the pointing hand. The raycast and the beam both honour it per hand. - Recenter moves from Grip to Bumper (saved grip bindings migrate), since Grip is now the laser toggle. - The downloads window replaces the browser panel instead of stacking over it, and browser input is suspended while it (or the confirm dialog) is open.
This commit is contained in:
1 parent
e39fd5e77c
commit
ee95d79efe
8 files changed
+84
-28
No files matched your search
@@ -47,17 +47,19 @@ stays usable while a panel is open.
|
||||
|
||||
| Input | Action |
|
||||
| --- | --- |
|
||||
| **Trigger** | Point and click: open the entry under the laser, or press a control-bar button |
|
||||
| **A** | Open the highlighted entry — folder navigates, video plays / streams. Outside the browser it is play / pause |
|
||||
| **X** / **Y** | Download the highlighted entry (folders confirm first, showing their size) |
|
||||
| **B** | Open or close the file browser; also closes a panel and cancels the download dialog |
|
||||
| **Grip** | Toggle **that hand's** laser pointer on/off. Turn one off to navigate with the thumbstick or d-pad without the beam interfering, and to choose which controller points |
|
||||
| Thumbstick | Move the highlight in a panel; left/right seeks while playing |
|
||||
| **A** | Open the highlighted browser entry — folder navigates, video plays / streams. Outside the browser it is play / pause |
|
||||
| **B** | Open or close the file browser; also closes the settings panel and cancels the download dialog |
|
||||
| **Trigger** | Laser pointer: click a panel row, or a control-bar button |
|
||||
| **Grip** | Recenter the view in front of you |
|
||||
| **Bumper** | Recenter the view in front of you |
|
||||
|
||||
The control bar has a dedicated **Download** button (arrow) that downloads the
|
||||
entry highlighted in the browser — folders ask for confirmation and show their
|
||||
size first. The **Downloads** button (list icon) opens the downloads manager,
|
||||
where you can pause, resume, stop and clear jobs, then play a finished file or
|
||||
open a finished folder.
|
||||
The control bar has a **Download** button (arrow) that downloads the entry
|
||||
highlighted in the browser, and a **Downloads** button (list icon) that opens the
|
||||
downloads manager — where you can pause, resume, stop and clear jobs, then play a
|
||||
finished file or open a finished folder. Opening the downloads manager replaces
|
||||
the file browser rather than stacking on top of it.
|
||||
|
||||
All button bindings can be changed in **Settings ▸ Controls** (press-to-bind).
|
||||
|
||||
|
||||
+28
-14
@@ -435,6 +435,20 @@ int App::run() {
|
||||
!settings_.isListening() && !settings_.isOpen() &&
|
||||
(in.buttonDown[XrInputState::BtnX] ||
|
||||
in.buttonDown[XrInputState::BtnY]);
|
||||
// Grip toggles that hand's laser pointer (SteamVR behaviour), so the
|
||||
// stick / d-pad can drive a panel without the beam fighting it, and so
|
||||
// the user picks which controller is the pointing hand.
|
||||
if (!settings_.isListening()) {
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
if (in.gripDown[h]) {
|
||||
const bool on = !renderer_.laserOn(h);
|
||||
renderer_.setLaserOn(h, on);
|
||||
PIXELVR_LOG_INFO("laser [%s]: %s",
|
||||
h == 0 ? "left" : "right",
|
||||
on ? "on" : "off");
|
||||
}
|
||||
}
|
||||
}
|
||||
bool interacted = false;
|
||||
bool browserOpen = false;
|
||||
bool settingsOpen = false;
|
||||
@@ -679,7 +693,9 @@ int App::run() {
|
||||
renderer_.setDownloadsModal(false);
|
||||
}
|
||||
// Publish the browser panel first so the pointer can raycast it.
|
||||
if (browser_.isOpen()) {
|
||||
// The downloads window replaces the browser view instead of stacking
|
||||
// on top of it, so nothing behind it can be selected by mistake.
|
||||
if (browser_.isOpen() && !dlWin) {
|
||||
std::vector<std::string> rows;
|
||||
rows.reserve(browser_.entries().size());
|
||||
for (const auto& e : browser_.entries()) {
|
||||
@@ -827,8 +843,10 @@ int App::run() {
|
||||
#ifdef PIXELVR_HAVE_FREETYPE
|
||||
// Browsing stays interactive during downloads: they run in a separate
|
||||
// process now, so they no longer block directory access. (Only guard
|
||||
// against an in-flight SMB directory scan.)
|
||||
if (browser_.isOpen() && !browser_.isScanning()) {
|
||||
// against an in-flight SMB directory scan.) Interaction is suspended
|
||||
// while an overlay (downloads window / confirm dialog) owns the view.
|
||||
if (browser_.isOpen() && !browser_.isScanning() && !downloadsWindow_ &&
|
||||
pendingDl_.empty()) {
|
||||
if (const int d = navStep(in.thumbstickY, nowT)) {
|
||||
browser_.move(d);
|
||||
renderer_.scrollBrowser(d);
|
||||
@@ -837,19 +855,15 @@ int App::run() {
|
||||
const int hovered = renderer_.browserHovered();
|
||||
if (hovered >= 0) {
|
||||
renderer_.setBrowserSelected(hovered);
|
||||
// The pointer drives the selection, so A acts on the entry
|
||||
// actually being pointed at (not just the last trigger press).
|
||||
browser_.setSelected(hovered);
|
||||
}
|
||||
// Trigger activates the selection whether it was made with the
|
||||
// laser (hovered row) or moved with the thumbstick. Skipped when
|
||||
// the pointer is on the control bar (that click is handled below).
|
||||
// A opens / activates the highlighted entry: folders navigate, video
|
||||
// files play. Downloading has its own button (X / Y), and when the
|
||||
// laser is resting on a control-bar button that button takes the
|
||||
// press instead (handled below).
|
||||
if (in.togglePlay && renderer_.pointerButton() < 0 &&
|
||||
pendingDl_.empty()) {
|
||||
// Open the highlighted entry. The trigger does it whenever the
|
||||
// laser is not resting on a control-bar button (that click belongs
|
||||
// to the bar, handled below); A does it too, which is the natural
|
||||
// choice when moving the highlight with the thumbstick / d-pad.
|
||||
// Downloading has its own buttons (X / Y).
|
||||
if ((in.togglePlay || in.select) &&
|
||||
renderer_.pointerButton() < 0) {
|
||||
std::string playPath;
|
||||
if (browser_.activate(playPath) == Browser::Result::Play &&
|
||||
!playPath.empty()) {
|
||||
|
||||
@@ -1015,9 +1015,10 @@ void VulkanRenderer::updatePointer() {
|
||||
}
|
||||
const Mat4 inv = rigidInverse(panelBase);
|
||||
|
||||
// Prefer the right hand; fall back to the left.
|
||||
// Prefer the right hand; fall back to the left. A hand whose laser is off
|
||||
// takes no part in the raycast.
|
||||
for (int h : {1, 0}) {
|
||||
if (!controllers_[h].active) {
|
||||
if (!controllers_[h].active || !laserOn_[h]) {
|
||||
continue;
|
||||
}
|
||||
const Quat q{controllers_[h].orientation[0], controllers_[h].orientation[1],
|
||||
@@ -2151,7 +2152,7 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const float rayCol[4] = {0.25f, 0.60f, 1.0f, 0.85f};
|
||||
const float cursorCol[4] = {1.0f, 1.0f, 1.0f, 0.95f};
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
if (!controllers_[h].active) {
|
||||
if (!controllers_[h].active || !laserOn_[h]) {
|
||||
continue;
|
||||
}
|
||||
const Quat q{controllers_[h].orientation[0],
|
||||
|
||||
@@ -202,6 +202,16 @@ public:
|
||||
};
|
||||
void setControlBar(bool visible) { controlBarVisible_ = visible; }
|
||||
|
||||
// Per-hand laser pointer toggle (grip press). A hand with the laser off
|
||||
// neither casts a beam nor takes part in the UI raycast, so the other hand
|
||||
// can own the pointer while this one drives a panel with its stick/d-pad.
|
||||
void setLaserOn(int hand, bool on) {
|
||||
if (hand >= 0 && hand < 2) laserOn_[hand] = on;
|
||||
}
|
||||
bool laserOn(int hand) const {
|
||||
return (hand >= 0 && hand < 2) ? laserOn_[hand] : false;
|
||||
}
|
||||
|
||||
// The control-bar button currently under the pointer, or -1 if none.
|
||||
int pointerButton() const { return pointerButton_; }
|
||||
// True when the pointer is over the draggable timeline; pointerScrub() is
|
||||
@@ -365,6 +375,8 @@ private:
|
||||
bool pointerActive_ = false;
|
||||
int pointerRow_ = -1;
|
||||
int pointerHand_ = -1;
|
||||
// Whether each hand's laser pointer is on (toggled with the grip button).
|
||||
bool laserOn_[2] = {true, true};
|
||||
float pointerHit_[3] = {0.0f, 0.0f, 0.0f};
|
||||
bool controlBarVisible_ = false;
|
||||
int pointerButton_ = -1;
|
||||
|
||||
@@ -343,7 +343,12 @@ void SettingsMenu::loadConfig() {
|
||||
else if (key == "distance") screenDistance_ = std::clamp(std::stof(val), 1.0f, 10.0f);
|
||||
else if (key == "bind_play_btn") bindPlayBtn_ = std::clamp(std::stoi(val), 0, kButtonCount - 1);
|
||||
else if (key == "bind_menu_btn") bindMenuBtn_ = std::clamp(std::stoi(val), 0, kButtonCount - 1);
|
||||
else if (key == "bind_recenter_btn") bindRecenterBtn_ = std::clamp(std::stoi(val), 0, kButtonCount - 1);
|
||||
else if (key == "bind_recenter_btn") {
|
||||
const int b = std::clamp(std::stoi(val), 0, kButtonCount - 1);
|
||||
// Grip used to default to recenter; it now toggles the laser pointer,
|
||||
// so a saved grip binding is migrated to the new default (Bumper).
|
||||
bindRecenterBtn_ = (b == 8) ? 6 : b;
|
||||
}
|
||||
else if (key == "download_dir") downloadDirIdx_ = std::clamp(std::stoi(val), 0, kDownloadDirCount - 1);
|
||||
else if (key == "audio_lang") audioLangIdx_ = std::clamp(std::stoi(val), 0, kLangCount - 1);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ private:
|
||||
// Function -> physical button (XrInputState::Button index); press-to-bind.
|
||||
int bindPlayBtn_ = 0; // default A
|
||||
int bindMenuBtn_ = 1; // default B
|
||||
int bindRecenterBtn_ = 8; // default Grip
|
||||
int bindRecenterBtn_ = 6; // default Bumper (grip toggles the laser)
|
||||
int listeningFunc_ = -1; // 0=play 1=menu 2=recenter while binding, else -1
|
||||
int downloadDirIdx_ = 0; // index into the download-destination presets
|
||||
|
||||
|
||||
@@ -543,6 +543,8 @@ void XrContext::syncActions() {
|
||||
input_.thumbstickRX = 0.0f;
|
||||
input_.thumbstickRY = 0.0f;
|
||||
for (int i = 0; i < XrInputState::BtnCount; ++i) input_.buttonDown[i] = false;
|
||||
input_.gripDown[0] = false;
|
||||
input_.gripDown[1] = false;
|
||||
if (!actionsReady_) {
|
||||
return;
|
||||
}
|
||||
@@ -628,6 +630,21 @@ void XrContext::syncActions() {
|
||||
st.isActive && st.currentState && st.changedSinceLastSync;
|
||||
}
|
||||
}
|
||||
|
||||
// Per-hand grip press: `buttonDown` above aggregates both hands, but the laser
|
||||
// pointer is toggled per hand, so query the squeeze action for each subaction.
|
||||
if (buttonActions_[XrInputState::BtnGrip] != XR_NULL_HANDLE) {
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
XrActionStateGetInfo gi{XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
gi.action = buttonActions_[XrInputState::BtnGrip];
|
||||
gi.subactionPath = handPaths_[h];
|
||||
XrActionStateBoolean st{XR_TYPE_ACTION_STATE_BOOLEAN};
|
||||
if (XR_SUCCEEDED(xrGetActionStateBoolean(session_, &gi, &st))) {
|
||||
input_.gripDown[h] =
|
||||
st.isActive && st.currentState && st.changedSinceLastSync;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XrContext::handleSessionStateChange(const XrEventDataSessionStateChanged& ev,
|
||||
|
||||
@@ -56,6 +56,11 @@ struct XrInputState {
|
||||
BtnDpadUp, BtnDpadDown, BtnDpadLeft, BtnDpadRight, BtnCount
|
||||
};
|
||||
bool buttonDown[BtnCount] = {};
|
||||
|
||||
// Per-hand grip (squeeze) rising edge: toggles that hand's laser pointer, so
|
||||
// one controller can drive a panel with the stick while the other points.
|
||||
// Index 0 = left, 1 = right.
|
||||
bool gripDown[2] = {false, false};
|
||||
};
|
||||
|
||||
// Owns the OpenXR instance, system, session, reference space, and swapchains,
|
||||
|
||||
Reference in new issue
Block a user