diff --git a/README.md b/README.md index be4985a..788cb0f 100644 --- a/README.md +++ b/README.md @@ -706,11 +706,15 @@ window until SteamVR restarts. Debugging: `window.__sfuiWindowCurvature.dump()` every element the patch drives has class `sfui-curv-ctl`; when a press becomes a drag, a bubbling `CustomEvent` `sfui-curv-dragstart` (detail `{ frameID, where: 'menu' | 'bar' }`) is dispatched on it, and -`sfui-curv-dragend` when it ends; `window.__sfuiWindowCurvature.cancelPress()` -ends a press without its click. A patch with its own gesture lets -`mousemove` through while undecided, drops its gesture on -`sfui-curv-dragstart`, and calls `cancelPress()` when it takes the press -over. +`sfui-curv-dragend` when it ends; +`window.__sfuiWindowCurvature.scalePressDragThreshold(factor)` sets the +current press's drag threshold to `factor` × `dragThresholdPixels` (from the +press start; returns whether it applied, i.e. a press that is not yet a +drag); `window.__sfuiWindowCurvature.cancelPress()` ends a press without its +click. 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. **Caveats:** found by signature (`window-curvature` in `signatures.json`); on mismatch the dashboard stays stock. Tested with SteamVR build 11008059. @@ -742,8 +746,10 @@ steamFrame.dashboard.frameControls = { ``` With [window curvature](#window-curvature-dashboardwindowcurvature), a drag -before the ring shows adjusts curvature; once the ring shows, the long press -wins. Debugging: `window.__sfuiFrameControls.dump()`, `.placement()`, +on the curvature control adjusts curvature and cancels the long press, also +after the ring shows; once the ring shows, the drag needs 3× the usual travel +(`dragThresholdPixels`, counted from where the press started), so laser +drift during the hold doesn't cancel it. Debugging: `window.__sfuiFrameControls.dump()`, `.placement()`, `.reset()` (forget choices), `.log`. **Limitations:** laser only (no right-click or thumbstick click reaches the diff --git a/modules/frame-controls/patch.js b/modules/frame-controls/patch.js index e572add..6cd6eae 100644 --- a/modules/frame-controls/patch.js +++ b/modules/frame-controls/patch.js @@ -34,9 +34,13 @@ // swallowed so its stock action doesn't run. // Contract with window-curvature (whose controls own press-and-drag): its // elements carry class sfui-curv-ctl; a drag there dispatches a bubbling -// CustomEvent 'sfui-curv-dragstart' (cancels the long press before the ring -// shows); once the ring shows, the long press takes over the press via -// window.__sfuiWindowCurvature.cancelPress(). Neither reads the other's +// CustomEvent 'sfui-curv-dragstart', which cancels the long press (a drag +// always wins). When the ring shows, the press's drag threshold is raised to +// CURV_RING_THRESHOLD x its own via +// window.__sfuiWindowCurvature.scalePressDragThreshold(), still measured from +// the press start: laser drift keeps the long press, a deliberate drag still +// cancels it. On completion the long press takes the press over via +// cancelPress() (no curvature click on release). Neither reads the other's // thresholds. // // Popup: its own scene-graph panel so the bar/menu panels don't change. The @@ -59,7 +63,7 @@ // setPlacement(name or "icon:N", 'bar' | 'menu' | null), reset(). ((find, sigs, opts) => { const NAME = 'frame-controls'; - const VERSION = 3; + const VERSION = 4; const T_SPACER = 1, T_ACTION = 2; const LS_KEY = 'sfui.frameControls.v1'; // Action icon enums (sigs.controls anchors them): names for the options. @@ -494,6 +498,7 @@ // ---- long press -------------------------------------------------------------------- const SVGNS = 'http://www.w3.org/2000/svg'; const RING_DELAY = Math.min(1000, o.longPressMs / 2); + const CURV_RING_THRESHOLD = 3; // x window-curvature's drag threshold once the ring shows const curvApi = () => window.__sfuiWindowCurvature; let press = null; // { hit, t0, timer, ringTimer, ring, done, curv } let swallow = null; // { until, button }: the click after a completed long press @@ -575,7 +580,7 @@ hit.button.classList.add('sfui-fc-pressing'); // positioning context for the ring p.ringTimer = setTimeout(() => { if (press !== p || p.done) return; - if (p.curv) curvApi()?.cancelPress?.(); // the long press takes the press over + if (p.curv) curvApi()?.scalePressDragThreshold?.(CURV_RING_THRESHOLD); // drift keeps the hold, a drag still wins p.ring = showRing(p); }, RING_DELAY); return; diff --git a/modules/window-curvature/patch.js b/modules/window-curvature/patch.js index c30af04..b1d1e4f 100644 --- a/modules/window-curvature/patch.js +++ b/modules/window-curvature/patch.js @@ -47,24 +47,31 @@ // Contract with other patches (e.g. frame-controls' long press); this patch // owns press-and-drag on its controls: // - Every element it drives has the class `sfui-curv-ctl`. -// - When a press becomes a drag (opts.dragThresholdPixels px vertical) it -// dispatches a bubbling CustomEvent `sfui-curv-dragstart` on the element -// (detail { frameID, where: 'menu' | 'bar' }); when that drag ends -// (release or cancelPress), `sfui-curv-dragend`. +// - When a press becomes a drag (opts.dragThresholdPixels px vertical from +// where the press started) it dispatches a bubbling CustomEvent +// `sfui-curv-dragstart` on the element (detail { frameID, where: 'menu' | +// 'bar' }); when that drag ends (release or cancelPress), +// `sfui-curv-dragend`. +// - window.__sfuiWindowCurvature.scalePressDragThreshold(factor) sets the +// current press's threshold to factor x opts.dragThresholdPixels, still +// measured from the press start, so small laser drift doesn't start a +// drag while a deliberate one still does; returns whether it applied +// (a press that is not yet a drag). The next press starts at 1x. // - 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 on these elements lets mousemove through while -// undecided, drops its gesture on `sfui-curv-dragstart`, and calls -// cancelPress() when it takes the press over. Neither side reads the other's -// thresholds or restores the other's state. +// undecided, drops its gesture on `sfui-curv-dragstart`, may raise the +// threshold with scalePressDragThreshold() 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. // // Debugging: window.__sfuiWindowCurvature: dump(), hits (rewrite/haptic // counters), log, setValue(frameID, v), controls(), cancelPress(), -// dragValue(v0, dy, ppu, cur). +// scalePressDragThreshold(factor), pressing, dragValue(v0, dy, ppu, cur). ((find, sigs, opts, hooks) => { const NAME = 'window-curvature'; - const VERSION = 19; + const VERSION = 20; const FLAT = 999; // origin distances >= this are "flat" const ICON_OFF = 40, ICON_ON = 39; // Toggle Curvature action icons (sigs.curvatureAction) const HAPTIC = { Snap: 3, Sliding: 4, SlidingEdge: 5 }; // EOverlayHapticEffect (sigs.hapticEffects) @@ -338,11 +345,12 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; } // Presses/clicks are stopped at the element so React (root listener) never // runs the stock onClick; a press that never became a drag is a click on // mouseup. Drag is vertical and relative (the cylinder bends horizontally, - // so y barely moves as curvature changes); after dragThresholdPixels px it + // so y barely moves as curvature changes); past the press's threshold + // (dragThresholdPixels, or as scaled by scalePressDragThreshold) it // re-bases (no jump). One press at a time, window capture listeners. const stop = (e) => e.stopPropagation(); const STOPPED = ['click', 'dblclick', 'mouseup', 'pointerdown', 'pointerup', 'contextmenu']; - let press = null; // { c, y0, v0, last, moved } + let press = null; // { c, y0, v0, last, moved, threshold } const emit = (c, type) => { try { @@ -359,7 +367,7 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; } const y = e.clientY; p.last = y; if (!p.moved) { - if (Math.abs(y - p.y0) < o.dragThresholdPixels) return; + if (Math.abs(y - p.y0) < p.threshold) return; p.moved = true; muteStock = true; p.c.el?.classList.add('sfui-dragging'); @@ -401,7 +409,7 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; } const f = FS.GetFrame(c.fid); if (e.button !== 0 || !f) return; endPress('new press'); - press = { c, y0: e.clientY, last: e.clientY, v0: shown(f), moved: false }; + press = { c, y0: e.clientY, last: e.clientY, v0: shown(f), moved: false, threshold: o.dragThresholdPixels }; window.addEventListener('mousemove', onMove, true); window.addEventListener('mouseup', onUp, true); }; @@ -536,8 +544,15 @@ ${ROW}:last-child > .sfui-curv-ind { margin-bottom: -14px; } version: VERSION, id, opts: o, hits, log: logBuf, state: S, teardown, check, dump, dragValue, setValue: (fid, v) => { const f = FS.GetFrame(fid); if (f) setValue(f, v, 'api'); return f ? shown(f) : 'no frame'; }, cancelPress: () => endPress('cancelled'), + scalePressDragThreshold: (factor) => { + const p = press; + if (!p || p.moved || !(factor >= 0 && Number.isFinite(factor))) return false; + p.threshold = factor * o.dragThresholdPixels; + log('press drag threshold', { frame: p.c.fid, where: p.c.where, px: p.threshold }); + return true; + }, controls: () => [...controls.values()].map((c) => ({ frame: c.fid, where: c.where, connected: !!c.el?.isConnected })), - get pressing() { return press && { frame: press.c.fid, where: press.c.where, dragging: press.moved }; }, + get pressing() { return press && { frame: press.c.fid, where: press.c.where, dragging: press.moved, threshold: press.threshold }; }, }; window.__sfuiWindowCurvature = state; log('patched', { version: VERSION, opts: o });