2 Commits
Author SHA1 Message Date
Pixelplanet 47836de7d8 docs: document the A-to-open / trigger-to-point control scheme and Download button 2026-10-15 20:25:38 +02:00
Pixelplanet 3164a84545 feat(input): A opens the browser selection, dedicated Download control-bar button
- The trigger was overloaded (control bar + list activation), so the bar was
  unreachable while the file browser was open. A now opens/activates the
  highlighted entry; the trigger is pointer-only.
- New CtrlButton::Download in the control bar downloads the focused entry
  (folders still prompt with the size). Downloads window button gets its own
  list icon so the two are distinguishable.
- Fix the raycast: panel hit tests used an early 'continue' when the ray missed
  the panel, which swallowed rays aimed at the control bar (drawn below the
  panel) and at the timeline/time readout. They now fall through to the bar.
2026-10-15 20:24:27 +02:00
6 changed files with 69 additions and 33 deletions

No files matched your search

+22
View File
@@ -40,6 +40,28 @@ V4L2 M2M decoders. For zero-copy dma-buf decoding, build the patched FFmpeg into
back to the system FFmpeg with CPU colour conversion.
## Controls
The laser pointer and the control bar are **separate** input paths, so the bar
stays usable while a panel is open.
| Input | Action |
| --- | --- |
| 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 |
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.
All button bindings can be changed in **Settings ▸ Controls** (press-to-bind).
## Repository layout
```
Binary file not shown.
+10
View File
@@ -122,6 +122,16 @@ void main() {
vec2(0.36, 0.30), vec2(0.0, -0.28)) - 0.03;
float base = sdRoundedBox(p - vec2(0.0, -0.62), vec2(0.52, 0.08), 0.03);
d = min(min(shaft, head), base);
} else if (shape == 13) {
// Shape 13: List / queue (three bars with leading dots) for the
// downloads window, so it is distinct from the plain download arrow.
float b1 = sdRoundedBox(p - vec2(0.10, 0.46), vec2(0.40, 0.07), 0.05);
float b2 = sdRoundedBox(p - vec2(0.10, 0.00), vec2(0.40, 0.07), 0.05);
float b3 = sdRoundedBox(p - vec2(0.10, -0.46), vec2(0.40, 0.07), 0.05);
float p1 = sdRoundedBox(p - vec2(-0.50, 0.46), vec2(0.10, 0.10), 0.05);
float p2 = sdRoundedBox(p - vec2(-0.50, 0.00), vec2(0.10, 0.10), 0.05);
float p3 = sdRoundedBox(p - vec2(-0.50, -0.46), vec2(0.10, 0.10), 0.05);
d = min(min(min(b1, b2), min(b3, p1)), min(p2, p3));
} else {
d = sdRoundedBox(p, vec2(1.0), 0.0);
}
+21 -16
View File
@@ -689,8 +689,8 @@ int App::run() {
// Keep any message (e.g. a download hint) visible; just add
// the action hint rather than replacing it.
status = status.empty()
? std::string("Point + Trigger: Stream A: Download")
: status + " [A: Download]";
? std::string("A: open Bar: download")
: status + " [A: open]";
}
renderer_.setBrowser(true, browser_.title(), rows,
browser_.selected(), status);
@@ -832,7 +832,10 @@ int App::run() {
// 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).
if (in.select && renderer_.pointerButton() < 0) {
// A opens / activates the highlighted entry: folders navigate, video
// files play. Downloading has its own control-bar button, so the
// trigger stays free to click panels and the bar.
if (in.togglePlay && pendingDl_.empty()) {
std::string playPath;
if (browser_.activate(playPath) == Browser::Result::Play &&
!playPath.empty()) {
@@ -842,19 +845,6 @@ int App::run() {
browser_.setOpen(false);
}
}
// A: download the focused entry; folders prompt for confirmation.
if (in.togglePlay && pendingDl_.empty()) {
const Browser::Entry* fe = browser_.focused();
if (fe != nullptr && fe->isDir && !fe->isBack &&
browser_.onNetwork()) {
pendingDl_ = fe->path;
pendingDlName_ = fe->label;
pendingDlFresh_ = true;
browser_.measureFolder(fe->path); // size for the dialog
} else {
browser_.downloadFocused();
}
}
}
if (browser_.isOpen() && in.recenter && in.headValid) {
renderer_.recenterScreen(in.headPos, in.headForward);
@@ -933,6 +923,21 @@ int App::run() {
#ifdef PIXELVR_HAVE_FREETYPE
else if (cb == VulkanRenderer::CtrlButton::Files) {
browser_.open();
} else if (cb == VulkanRenderer::CtrlButton::Download) {
// Download the entry highlighted in the file browser;
// folders ask for confirmation (with the size) first.
if (browser_.isOpen() && pendingDl_.empty()) {
const Browser::Entry* fe = browser_.focused();
if (fe != nullptr && fe->isDir && !fe->isBack &&
browser_.onNetwork()) {
pendingDl_ = fe->path;
pendingDlName_ = fe->label;
pendingDlFresh_ = true;
browser_.measureFolder(fe->path);
} else {
browser_.downloadFocused();
}
}
}
#endif
#ifdef PIXELVR_HAVE_MEDIA
+13 -16
View File
@@ -65,7 +65,7 @@ constexpr float kBarY = -1.16f;
constexpr float kBtnHalfW = 0.08f;
constexpr float kBtnHalfH = 0.06f;
constexpr float kBtnSpacing = 0.18f;
constexpr int kBtnCount = 7;
constexpr int kBtnCount = 8;
inline float ctrl_button_x(int i) {
return (static_cast<float>(i) - 0.5f * (kBtnCount - 1)) * kBtnSpacing;
}
@@ -1039,12 +1039,12 @@ void VulkanRenderer::updatePointer() {
const Vec3 world = origin + dir * t;
// The downloads window / confirmation modal takes priority: it is shown
// over the browser, so test it before the browser panel.
if (downloadsVisible_) {
if (hx < -kPanelW * 1.4f || hx > kPanelW * 1.4f ||
hyy < -kPanelTop * 1.3f || hyy > kPanelTop * 1.3f) {
continue;
}
// over the browser, so test it before the browser panel. Note the bounds
// are tested as part of the condition (not an early `continue`) so that a
// ray aimed at the control bar below the panel still falls through to the
// control-bar hit test further down.
if (downloadsVisible_ && hx >= -kPanelW * 1.4f && hx <= kPanelW * 1.4f &&
hyy >= -kPanelTop * 1.3f && hyy <= kPanelTop * 1.3f) {
pointerActive_ = true;
pointerHand_ = h;
pointerHit_[0] = world.x;
@@ -1074,10 +1074,8 @@ void VulkanRenderer::updatePointer() {
break;
}
if (browserVisible_) {
if (hx < -kPanelW || hx > kPanelW || hyy < -kPanelTop || hyy > kPanelTop) {
continue;
}
if (browserVisible_ && hx >= -kPanelW && hx <= kPanelW &&
hyy >= -kPanelTop && hyy <= kPanelTop) {
pointerActive_ = true;
pointerHand_ = h;
pointerHit_[0] = world.x;
@@ -1098,10 +1096,8 @@ void VulkanRenderer::updatePointer() {
break;
}
if (settingsVisible_) {
if (hx < -kPanelW || hx > kPanelW || hyy < -kPanelTop || hyy > kPanelTop) {
continue;
}
if (settingsVisible_ && hx >= -kPanelW && hx <= kPanelW &&
hyy >= -kPanelTop && hyy <= kPanelTop) {
pointerActive_ = true;
pointerHand_ = h;
pointerHit_[0] = world.x;
@@ -1607,7 +1603,8 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
case CtrlButton::Stop: iconShape = UiShape::Stop; break;
case CtrlButton::Files: iconShape = UiShape::Files; break;
case CtrlButton::Settings: iconShape = UiShape::Settings; break;
case CtrlButton::Downloads: iconShape = UiShape::Download; break;
case CtrlButton::Downloads: iconShape = UiShape::List; break;
case CtrlButton::Download: iconShape = UiShape::Download; break;
}
const float iconCol[4] = {0.96f, 0.96f, 0.98f, 1.0f};
const float iconHalfW = kBtnHalfW * 0.48f;
+3 -1
View File
@@ -176,6 +176,7 @@ public:
Checkmark = 10,
Stop = 11,
Download = 12,
List = 13,
};
// The browser row currently under the pointer, or -1 if none.
@@ -196,7 +197,8 @@ public:
Stop = 3,
Files = 4,
Settings = 5,
Downloads = 6
Downloads = 6, // open the downloads window (list of jobs)
Download = 7 // download the focused file-browser entry
};
void setControlBar(bool visible) { controlBarVisible_ = visible; }