Files

4.0 KiB
Raw Permalink Blame History

Window curvature

dashboard.windowCurvature.*, module window-curvature (options).

A SteamVR dashboard patch.

Problem

SteamVR dashboard windows are either curved (fixed radius) or flat, and world windows start flat.

What you get

The "Toggle Curvature" row of a window's three-dot menu becomes a control showing the window's value; the same control in the bottom bar (see window control bar) works without the value, with haptic steps.

  • click: curved → flat, flat → stock (1);
  • drag up/down with the laser: curvature from 0 (flat) to max, relative to stock (2 = half the radius), rounded to step, with a detent of detentPixels of drag at each of detentPoints (no values skipped), and haptics (haptics) for detents, edges and steps. Drag distance: dragPixelsPerUnit in the menu, barDragPixelsPerUnit on the bar button, after dragThresholdPixels.

A window without its own value is shown at initial once curved in the world or on a hand, at 1 in the dashboard or theater. Values are kept per window until SteamVR restarts.

Configuration

steamFrame.dashboard.windowCurvature = {
  enable = true;
  # initial = 1.0;  max = 3.0;  step = 0.05;
  # detentPoints = [ 0 1.0 ];  detentPixels = 24;
  # dragPixelsPerUnit = 60;    # full range in one drag (see below)
};

Limitations

  • Laser only; with gamepad navigation the row is the stock toggle.
  • No thumbstick scrolling (SteamVR sends no wheel events to the menu).
  • The laser stops at the menu's edge (~190 px above the row): with the default 120 px per 1.0, 0 → 1 fits into one drag, 0 → 3 takes two. Lower dragPixelsPerUnit (≤ 60) for the full range in one drag.

How it works

Stock curvature: the frame renders a transform frame:<id>:curvature-origin at z = DashboardStore.curvatureDistance when curved, else 1000; its panels reference it as curvature-origin-id and vrcompositor bends them onto a cylinder around it (curvature = 1/radius). Those MobX properties are non-configurable, so the patch hooks the mailbox SendMessage (shared hooks, shared with dashboard windows) and rewrites the origin's z in outgoing scene graphs to stock / value. On/off stays the stock toggle state, so stock toggle and wheel always agree.

Values are kept per window (key: first overlay key) in window.__sfuiWindowCurvatureState, across re-patch and unpatch, not across a dashboard reload. The bar button uses the coordinates SteamVR keeps sending past the pressed panel's edge while the trigger is held; the bar panel is never resized.

Debugging: window.__sfuiWindowCurvature.dump() (.log recent events).

Contract for other patches

For patches handling presses on the curvature controls (e.g. the window control bar's long press); the curvature patch owns press-and-drag on them:

  • Every element the patch drives has class sfui-curv-ctl.
  • When a press becomes a drag (dragThresholdPixels vertical from where the press started), a bubbling CustomEvent sfui-curv-dragstart (detail { frameID, where: 'menu' | 'bar' }) is dispatched on the element, and sfui-curv-dragend when that drag ends (release or cancelPress()).
  • window.__sfuiWindowCurvature.scalePressDragThreshold(factor) sets the current press's drag threshold to factor × dragThresholdPixels, still measured from the press start; returns whether it applied (a press that is not yet a drag). The next press starts at 1×.
  • window.__sfuiWindowCurvature.cancelPress() ends the current press without its click (a drag keeps its value); returns whether a press was active.

A patch with its own gesture lets mousemove through while undecided, drops its gesture on sfui-curv-dragstart, may raise the threshold while its gesture is under way, and calls cancelPress() when it takes the press over. Neither side reads the other's thresholds or restores the other's state.