Files
FauxxYT--StreamXR/docs/Spatial-Layout.md
T
2026-10-15 14:42:17 -07:00

22 KiB
Raw Permalink Blame History

Spatial layout (UX plan, in progress)

A plan to arrange StreamXR's VR interface in space the way Android XR and Material 3's XR guidance describe: panels, orbiters (floating toolbars and rails around a panel) and popups / dialogs raised towards you on fixed depth levels. Written 2026-10-14, after the Material 3 Expressive redesign (Materials-Redesign.md) finished the look. This is about where things are and how you move between them, not how they look. The user chose proposal C (with search in the middle; see "Decided"). Built so far: the Safe Zone (0.63.0) and the side panels with their toolbars, the controls under the zone and the Settings pill (0.64.0), the search bar and the middle results panel (0.66.0), the raised menus and dialogs (0.67.0).

A mock-up (front and side views, to scale from vr-panel/config.js) compares today with the three proposals below.

Sources

Material 3 XR: design overview, interaction, layout, accessibility; components: overview, app bars, dialogs, nav bar, nav rail, toolbars. Android XR: get started, foundations, spatial UI, scale, sizes and visual design, XR app quality guidelines.

Units: Android XR's dp are angular. 1 dp = 0.868 dmm (distance-independent millimetres), so at a distance of d metres, 1 dp ≈ 0.868 × d mm (at 1 m ≈ 0.87 mm; at 1.6 m ≈ 1.39 mm).

What the guides say (the parts that matter for StreamXR)

  • Placement. A panel's centre 1.75 m away (0.75-5 m allowed), its vertical centre 5° below eye level ("people tend to look down"); primary content in the central 41° of the view. Default panel 1024 × 720 dp (about 1.56 × 1.09 m at 1.75 m, about 48° wide), 32 dp corners. Let people recenter and pull the UI closer; design for sitting, standing and lying back. Avoid head-locked UI (a lazy follow at most).
  • Orbiters: floating UI that controls a panel, 20 dp outside its edge (or 12 dp overlapping it), raised 16 dp in front of it, never wider / taller than the panel. Use them sparingly: a few key navigation pieces.
    • App bar orbiter: one per app, "global", centred along the top of the app.
    • Navigation rail orbiter: app-level navigation, centred on the left (or right) edge, no taller than the panel, never between two panels; it can carry a prominent action button (the "spatialized FAB") just above or below it.
    • Navigation bar orbiter: the alternative, centred under the panel; the top is for the app bar.
    • Toolbar orbiter: actions for one panel, centred under it ("local context"); more than five items can expand and collapse within the panel's width.
  • Depth levels ("spatial elevation"): panel 0; orbiters 16 dp; popups (menus, dropdowns) 32 dp; dialogs 56 dp, the highest, with a dim layer (scrim) left on the panel. Dialogs fade and scale in while rising, with standard, long, non-bouncy easing. Don't raise large areas (sheets) or scrolling content.
  • Media apps: playback controls in their own panel or orbiter, not over the video; offer a dimmed or virtual surround.
  • Targets: 56 × 56 dp recommended (48 dp minimum), 48 dp visuals, 8 dp between targets, no overlapping targets. At 1 m: 56 dp ≈ 4.9 cm.
  • Text: 14 dp or larger (≈ 1.2 cm at 1 m), normal weight or heavier, sentence case, short lines; let people scale text.
  • Motion: standard easing and long durations for anything large; no continuous motion of the world; stable frame rate (90 Hz: under 11.1 ms a frame).
  • Feedback: hover, focus and pressed states (or a raise in depth); spatial audio optional; controller haptics, with a way to turn them off.
  • Scrolling: lists and carousels keep moving briefly after you let go (momentum).
  • Navigation: a way back always available. Input: hands as the baseline, with controllers as an extra (Android XR); one hand is enough for everything.

What YouTube on Android XR does (the maintainer's research, 2026-10-14)

From three recordings (two of them Google's "Simulated" demos) and the talk "Designing for Wired XR Glasses: Key Considerations"; the full notes stay with the maintainer. In short:

  • Immersive playback (VR180, Google's demo): the video fills the view, no frame. The UI is three separate floating panels around it, dark rounded cards angled towards you: left: about this video (title, channel + Subscribe, Like / Dislike / Save / more, a description card, a comments teaser); bottom centre: slim controls below eye level (back, title, exit-immersive; volume, −10 / play / +10, settings; time, the red progress bar); right: recommendations (a scrolling list, leaning to 180° / 360° videos while immersive). A YouTube logo orb floats top-left. Panels are world-locked, and all of them fade out after a few seconds of not being used.
  • Windowed playback: the large-screen app in one floating window (system minimize / close pill above it), controls drawn over the video as on a TV; other apps' windows beside it.
  • Google's adaptive "video app" example (a Material 3 mock modelled on YouTube, not YouTube): a navigation rail floating to the left (Home, Shorts, Subs, Library), a search field in the top app bar, the video and its details in the main panel, recommendations in their own panel to the right (filter chips on top), and on headsets comments in their own column with a reply field; on glasses, comments collapse to a teaser and everything draws in towards the centre.
  • The talk: dark mode by default; no pure black surfaces (use the surface tokens); 56 dp targets; clear hover / pressed states; use depth for hierarchy ("in XR there's no need for boundaries"); show extra UI when looked at (navigation appearing when you look up); world-anchor UI for a user who stays put, soft-follow for one who moves (glasses). Most of the glasses advice (additive displays, dimming, soft follow) doesn't apply to the Steam Frame (an opaque headset, used sitting or standing still).

What already matches: world-locked panels, auto-hide after a few seconds (6 s here), the red progress bar, M3 surface tokens instead of black, controls below eye level, hover / pressed states. What doesn't: YouTube keeps the video clear (panels at the sides, controls low) where StreamXR stacks the panels in front of it, and splits "about this video" from "what to watch next".

Where StreamXR stands (0.62)

Measured from config.js (the mock-up prints these):

Today Guide
Open panel (Browse) centre −7°, 50° wide × 34° tall, 1.08 m away centre −5°, ≈ 48° wide, 1.75 m
Toolbar between the controls and the panel, −28° navigation never between panels
Controls panel centre −37°, 44° × 12°, 1.0 m an orbiter under its panel
Whole stack +10° to −43° tall primary content in the central 41°

Already as the guides ask: one panel at a time; panels fixed in the room with Recenter (no head lock); move / resize; hover, pressed and selected states and the white ring; motion; controls in their own panel (not over the video); a dark surround; frames within the budget (smooth in the headset); text at or above 14 dp; haptics on hover and click (config.js haptics, no off switch yet); a passthrough option for when browsers offer it.

Gaps:

  1. Height of the stack. The open panel is about right; the controls panel sits 37° down (a long look down), so the whole thing spans about 53° vertically.
  2. Navigation between panels. The toolbar sits between the controls and the open panel, where the guides say navigation is hard to find.
  3. No depth for popups and dialogs. The controls panel's menus, the search suggestions and "Unsubscribe?" are flat in their panel (only the Previous / Next hover card is raised, 4 cm).
  4. Target sizes at about 1 m (56 dp ≈ 4.9 cm): controls panel buttons ≈ 6.9 cm and toolbar items ≈ 5.6 cm pass; chips ≈ 3.4 cm, tabs ≈ 4.1 cm, Settings switches ≈ 3.4 cm tall and page arrows ≈ 4.4 cm are under it.
  5. Scrolling has no momentum (a drag stops dead).
  6. No text size setting. No haptics off switch.
  7. Motion: opening a panel has a slight overshoot (motion.js open spring, damping 0.8); the guides want no bounce for large movements.
  8. Back exists only inside some panels.
  9. Hands: controller-only (Chrome on SteamVR may not expose hand tracking; future).

The proposals

A. Layered, in front of the video (the maintainer's idea, mapped onto the depth levels)

  • Level 0: the panel (Browse, Description...), 1.6 m away, centre 5° below eye level, scaled up so it looks the same size as today (46° × 31°).
  • Level 1 (16 dp ≈ 2.2 cm in front): three orbiters, 20 dp outside the panel's edges:
    • a navigation rail on the left edge, replacing the toolbar (icons with short labels; it can expand inward to show names, as the toolbar does now);
    • a search app bar along the top: the search field; typing opens the keyboard and its suggestions;
    • the controls along the bottom (centre about −27° instead of −37°).
  • Level 3 (32 dp ≈ 4.4 cm): popups: the controls' menus (quality, speed, format, volume), the search suggestions, the Previous / Next card.
  • Level 5 (56 dp ≈ 7.8 cm): dialogs: confirmations ("Unsubscribe?"), with a scrim on the panel.

The levels are a few centimetres apart, not tens: enough to read as layers without your eyes refocusing, or the laser hitting the wrong layer at an angle.

B. The video in the middle, panels beside it (Android XR's media pattern)

The cinema screen stays straight ahead; the open panel sits to its side in a curved row (turned to face you, about 42° to the right), with the rail on its outer edge and the search app bar on top; the controls go under the video. Nothing covers the video, but browsing means turning your head about 40°, and a 360° video has no screen to sit beside.

C. YouTube's immersive layout: info left, controls low, Up Next right

The arrangement of YouTube's own VR180 demo, with StreamXR's panels sorted into its zones:

  • Middle: the video, left clear. The controls sit low in the middle (about 18° down, 1.7 m), slimmer than today if possible (YouTube's has three rows: title and exit; the transport; the time and progress bar).
  • Left (about 38° out, turned to face you): "about this video": Description, Channel, comments, the things about what's playing; the navigation rail on its outer edge.
  • Right (about 33° out): "what to watch": Up Next, Browse, Search results, with the search app bar over it.
  • Settings opens from the controls' gear as a popup (level 3), as YouTube's does.
  • The same zones in a 360° / 180° video (they float in it, as in YouTube's demo).
  • Auto-hide stays (all of it fades after a few seconds unused).

Trade-offs

  • A keeps everything in the comfortable middle and reuses today's habits (look down to the controls, panels above them), but the panels cover part of the video (as today; they hide themselves while it plays).
  • B never covers the video and suits a cinema, but needs head turns for browsing, only fits the 2D screen, and changes the most.
  • C is what YouTube does: the video stays clear, two panels can be open at once (what's playing and what's next), it works the same for 2D and 360° / 180°, and it follows a layout people will meet in YouTube's own XR app. Costs: a glance of about 35° to each side, the most rework (two panel zones, Settings as a popup, a slimmer controls panel), and each zone must fit its panels' sizes.
  • Both: the search app bar makes Search a bar rather than a panel; the rail makes the current toolbar and the panels' own header tabs rethinkable (panel tabs stay: they're inside the panel).

Steps (each released and checked in the headset)

  1. Comfort placement: the stack 1.6 m away and scaled to match, the open panel's centre 5° below eye level, the controls just under it; the keyboard where the controls are. (Changes the maintainer's earlier placement choice: needs their OK.)
  2. Orbiters: the navigation rail (left), the search app bar (top), the controls as the bottom orbiter, all raised 16 dp; the toolbar retired.
  3. Popups and dialogs raised: menus and suggestions at 32 dp, confirmations at 56 dp with a scrim; fade and scale in while rising.
  4. Fixes (built in 0.73.0, without the Back: see the end): target sizes (chips, tabs, switches, arrows to ≥ 56 dp), scroll momentum, a text size setting and a haptics switch (Settings → Appearance), no-bounce easing for whole panels, a Back on the rail.

What it would touch

  • vr-panel.js (placement, restack, hit testing across depth levels, the keyboard's place), config.js (distances, sizes), chevrons-ui.js (becomes the rail), the Catalog style (Search as a bar), panel-ui.js (menus drawn on their own raised canvas), motion.js (the open spring).
  • The laser: hits on several layers at once must pick the nearest (already done for overlapping panels; check the few-centimetre gaps).
  • design-choices.md: the placement (1.0 m ahead, 0.78 m down), the chevron row and "Search is its own panel" would change, by the maintainer's decision.

Decided (the maintainer, 2026-10-14)

  • Proposal C, with one change to search: the search bar at the top (middle); while you type, its suggestions show in the middle; the results open in a middle panel, which goes away when the search bar is cleared. So the right zone keeps Up Next / Browse, and Search is no longer a panel in it.
  • A toolbar per side panel, on the Safe Zone line (upright M3E floating toolbars, just outside the zone's side edges, between the video and the panel; pointing at one grows its label outward, over its panel): left = Description, Channel, Comments (Comments becomes its own panel); right = Up Next, Browse. Each opens / switches / closes only its side. Each panel's inner edge lines up with its toolbar's middle (the toolbar straddles the edge, in front of it). The middle toolbar retires. (The guides put a rail on a panel's outer edge; the maintainer chose the zone line, so the buttons are the nearest thing to the video.)
  • Side panels narrower and taller than today's (there's more room up and down than across): about 900 × 1100 px (0.70 × 0.86 m at today's text size, about 35° × 43°); Up Next's grid becomes 2 × 3 cards instead of 3 × 2.
  • The cinema screen keeps its own size and distance (for now).
  • Settings: a compact tonal pill (icon + "Settings") centred on the controls panel's top edge (an orbiter overlapping it 12 dp, raised 16 dp); its popup rises from it.
  • Up Next stays Recommended, or the current playlist / mix's videos; Browse stays Browse (what they show doesn't change; only their shape, above).
  • First, the Safe Zone (below), so the new places are measured against something the user can tune for their headset.

The Safe Zone (built in 0.63.0)

The part of your view the layout is placed against, set as shares of what the headset shows, so the same settings fit any headset (vr-panel/safe-zone.js; Settings → Safe zone, safe-zone-options.js):

  • Width: a share of the horizontal field of view (default 50%; 40-100%).
  • Height: a share of the vertical field of view (default 45%; 40-100%).
  • Position: its centre above or below eye level, a share of the vertical field of view (default −1%; −25% to +25%).
  • The field of view is both eyes together, measured on the first frame from each eye's projection (canted displays included). The zone is fixed in the room from where you were and faced at the last recenter, like the panels. ns.vrSafeZone.angles() gives its edges in degrees (left / right / top / bottom from straight ahead). The zone protects the video (the maintainer, 2026-10-14): the left and right panels open outside its side edges, the search bar above its top, the controls below its bottom; only what you ask for comes inside it (search suggestions and results, the Settings popup).
  • While its settings tab is open, an outline (and a small cross at its centre) shows it, 1.7 m away, over everything.
  • The defaults are the maintainer's, set in the headset once the panels were placed (0.65.0).

The side panels (built in 0.64.0)

vr-panel.js ("the layout") places everything from the Safe Zone's edges (ns.vrSafeZone.angles()), on a sphere config.js layout.distance (1.7 m) around where your eyes were at the last recenter, each piece turned to face you:

  • Controls panel: in the middle, its top edge 20 dp (about 1°) under the zone's bottom. The keyboard takes its place, hanging from the same top edge.
  • Settings pill (orbiters-ui.js createPill): centred on the controls panel's top edge, overlapping it 12 dp, raised 16 dp; the Settings popup rises from it (8 dp above), raised 32 dp (the popup level). The registration's zone: 'controls'.
  • Side toolbars (orbiters-ui.js createToolbar): upright M3E floating toolbars, their bars 10 dp outside the zone's side edges at the zone's middle height, raised 16 dp; a pointed-at icon grows its name outward over the panel. Everything around the bar is see-through to the laser (solidAt).
  • Side panels: their inner edge under the middle of the toolbar's bar, centred on the zone's middle height, one open per side (zone: 'left' | 'right'; remembered).
  • Sizes: every canvas at layout.pxPerDegree (26) px per degree, so text looks as big as before; side panels 900 × 1100 px (about 35° × 43°); the grids keep today's card size and fill the height (3 × 3).
  • Changing the Safe Zone (or recentering) lays everything out again, which also brings back a controls panel you moved.
  • 0.65.0: toolbars ordered top to bottom: Description, Comments, Channel (left); Search, Up Next, Browse (right); Browse's icon is Material Symbols' "browse". The toolbars are solid while their side has a panel open (glass when it has none, 0.65.2: the panel's edge doesn't show through), and fade out after half the auto-hide time while playing.

The search bar (built in 0.66.0)

  • A registration with zone: 'top' (YouTube's Search) gets a search bar (orbiters-ui.js createSearchBar: grid.js's field and its suggestions menu) in the middle, its field 20 dp above the Safe Zone's top edge, raised 16 dp (glass, turning solid while the results panel is open, as the side toolbars do: 0.66.3); the suggestions drop from it into the zone while you type.
  • Its controller is asked every frame while the panels are up (the bar always shows) and returns, besides the panel's model, bar (the field) and open. While open (Search: once there's a search, until the bar is cleared), the panel (the results) shows in the zone's middle, its top edge 20 dp under the zone's top, shrunk if the zone is too short (or narrow) for it, leaving room for the Settings pill.
  • A panel asking for keys (host.typeInto(true)) brings up the in-VR keyboard; giving them back puts it away, if it came up that way.

Popups and dialogs raised (built in 0.67.0)

  • Menus (popups-ui.js createMenu): the controls panel's Quality, Speed and Format choices and the Volume slider no longer replace its button row; they float 32 dp in front of it (the popup level) as an M3 menu card rising from the button that opened it (which shows selected meanwhile), growing in and fading (the "open" spring). They close on a choice, on the button again, or on a click on nothing.
  • Dialogs (popups-ui.js createDialog, the shell's confirm(d), given to panels as host.confirm and to the site's onControl as shell.confirm): an M3 dialog in the Safe Zone's middle, 56 dp in front of the panels (the dialog level), over a scrim (a dim sheet 48 dp in front) that darkens everything behind; it rises without bouncing (the "dialog" spring). It's modal: only it can be pointed at; a click anywhere off it cancels. First use: "Unsubscribe from ?" (the controls panel and the Channel panel).

The fixes (built in 0.73.0)

  • Targets (kit.js regionAt, used by every panel style and the controls panel): a control drawn smaller than 56 dp (≈ 72 px at the layout's density) reaches out to that size around its middle, without changing its look (chips, tabs, switches, page arrows, the seek bar). Where reaches overlap, the nearest control wins; a point right on a control always picks it.
  • Momentum (vr-panel.js startFling / stepFling): a trigger drag let go while moving keeps scrolling at its speed and slows down (about a second), stopping at either end, when the panel goes away, on any trigger press or on the stick.
  • Panel size (Settings → Appearance, 80-130% in 5% steps, remembered): everything (text, buttons, panels, the toolbars, the keyboard) grows or shrinks together; the layout around the Safe Zone is done again. The user chose this over a text-only size (2026-10-14), which would have reflowed every style.
  • Vibration (Settings → Appearance, on by default, remembered): the controllers' buzz on pointing and clicking. Greyed out, with a note, while no controller can vibrate, which is always the case on the Steam Frame: Chrome gives its controllers no vibration API (0.73.1).
  • No bounce for whole panels (motion.js "panel" spring: the "open" spring's speed, damped): showing / hiding everything, a side panel opening, the toolbars and search bar, the keyboard swap. The floating menus keep the livelier "open" spring.
  • Back: not built (the maintainer, 2026-10-14). There's no rail to put it on; panels open and close from the toolbars, and the Channel panel has its own Back. It can come back on demand.