diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor b/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor index e7289a2..f979e0a 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizerControlPopover.razor @@ -15,13 +15,15 @@ mild modal tint (alpha from the single --deepdrft-modal-scrim-alpha token, §10.5). There is therefore no AnchorOrigin/TransformOrigin: a centered modal has no anchor (Phase 15 drops those parameters). - KNOB-DRAG SAFETY (Phase 15 §4, highest-risk detail): RadialKnob mounts its own full-viewport - position:fixed; z-index:9999 mouse-capture div WHILE dragging (RadialKnob.razor lines 5–9). That capture - div sits ABOVE the overlay scrim, so a knob drag's pointer-up lands on the capture div, never the scrim - — the overlay's OnClick does not fire mid-drag, so releasing the mouse outside the panel does NOT close - the modal. AutoClose is left OFF (the default) and dismissal is via the explicit scrim OnClick only, - carrying the SharePopover idiom forward under the new primitive. The panel stops click propagation so a - click INSIDE it never bubbles to the scrim's close handler. + KNOB-DRAG SAFETY (Phase 15 §4, highest-risk detail): RadialKnob calls setPointerCapture on its own + knob element when a drag begins, so all pointermove/pointerup/pointercancel events for that pointer are + delivered to the knob element regardless of where the cursor moves. The synthesised click on pointerup + is also retargeted to the knob, not to whatever element is under the cursor — so releasing outside the + panel never fires the scrim's close handler. The full-viewport position:fixed; z-index:9999 div + RadialKnob renders while dragging is a belt-and-suspenders UX guard (blocks stray clicks on underlying + content) but is no longer the load-bearing anti-dismiss mechanism. AutoClose is left OFF (the default) + and dismissal is via the explicit scrim OnClick only. The panel stops click propagation so a click + INSIDE it never bubbles to the scrim's close handler. The host owns NO control state and NO JS interop. The hosted WaveformVisualizerControls panel mutates the shared WaveformVisualizerControlState and raises Changed; the visualizer bridge subscribes. This diff --git a/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor b/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor index 8f7bb35..e8ada3f 100644 --- a/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor +++ b/DeepDrftPublic.Client/Controls/WaveformVisualizerControls.razor @@ -3,19 +3,19 @@ @using DeepDrftPublic.Client.Services @inject WaveformVisualizerControlState ControlState -@* The waveform visualizer control PANEL (Phase 12 §3d-revised → Phase 15 re-layout). The control deck for - the lava-lamp visualizer: a deterministic THREE-ROW, sectioned layout that encodes what the visualizer - composes — a LAVA field and a WAVEFORM ribbon, each independently toggleable (Phase 15 §3): +@* The waveform visualizer control PANEL (Phase 12 §3d-revised → Phase 15 re-layout → Phase 15 polish). + The control deck for the lava-lamp visualizer: a deterministic THREE-ROW, sectioned layout that encodes + what the visualizer composes — a LAVA field and a WAVEFORM ribbon, each independently toggleable (§3): - Row 1 (MODE, always): lava lamp-toggle, waveform lamp-toggle, [collisions knob — only when BOTH on], - then the color knob pinned far-right (applies to the whole field, so always visible). + Row 1 (MODE, always): lava lamp-toggle, waveform toggle (new waveform glyph), [collisions knob — only + when BOTH on], then the color knob pinned far-right (applies to the whole field, always visible). Row 2 (LAVA, only when lava on): "LAVA:" label + gravity / heat / fluid-amount / fluid-viscosity. - Row 3 (WAVE, only when waveform on): "WAVE:" label + scroll SLIDER + width knob pinned far-right. + Row 3 (WAVE, only when waveform on): "WAVE:" label + scroll RadialKnob + width RadialKnob (far-right). - The two lamp toggles are iconographic (lit LavaLampFilled / unlit LavaLamp glyph), green because they - are INTERACTIVE (the §5 colour principle: green = interactive, light = non-interactive). The eight - continuous dials are unchanged in tuning; the one widget-type change is scroll/zoom → a MudSlider (§8, - bound to ScrollSpeed alone). None of these is a seek surface (read-only contract §D). + The two toggles have STRONG ACTIVE-STATE styling: when ON the toggle chip has a green-accent background + (unmistakably active); when OFF it is muted/dim. The lava toggle keeps the lava-lamp glyph; the waveform + toggle uses a new distinct waveform-bars glyph (DDIcons.Waveform / WaveformFilled). Green = interactive + (§5 colour principle); light = non-interactive. All colours are token-sourced — no hardcoded hex. This is the PANEL CONTENT hosted inside WaveformVisualizerControlPopover, now a screen-centered tinted MudOverlay (Phase 15 §4). Because the overlay PORTALS its content out of this component's DOM subtree, @@ -24,10 +24,6 @@ scoped .razor.css carries only the legacy inline-bar fallback (Mix's old non-portaled mount), which may now be dead post-Phase-12 but is left in place — flagged, not cut (out of Phase 15 scope). - COLOUR PRINCIPLE (§5): the lamp toggles + knob arcs/pointers + the slider track/thumb are green-accent - (interactive); the "LAVA:"/"WAVE:" section labels and the knob caption icons are LIGHT (static). All - colours are token-sourced (deepdrft-tokens.css) — no hardcoded hex. - It owns NO JS interop: it mutates the shared, session-scoped WaveformVisualizerControlState and raises its Changed event. The visualizer bridge (WaveformVisualizer) subscribes and pushes the affected dial / subsystem-enable to the WebGL module. RadialKnob has no icon slot (its Label renders as SVG text) and no @@ -43,7 +39,7 @@