7aeced6a8d
Build WaveformVisualizerControlPopover pairing the lava-lamp trigger with the eight-knob WaveformVisualizerControls panel; style to the NowPlaying Hero look from tokens. Panel chrome scoped to the popover mount via a PanelChrome flag; Mix's inline mount unchanged.
186 lines
9.4 KiB
Plaintext
186 lines
9.4 KiB
Plaintext
@namespace DeepDrftPublic.Client.Controls
|
|
@using DeepDrftPublic.Client.Services
|
|
@inject WaveformVisualizerControlState ControlState
|
|
|
|
@* The waveform visualizer control PANEL (Phase 12 §3d-revised). EIGHT continuous RadialKnobs — scroll
|
|
speed, gradient rotation speed, lava gravity, lava heat, fluid amount, fluid viscosity, collision
|
|
strength, waveform width — each its own dedicated control with a Material-icon caption. The single
|
|
"bubbles" knob is split into fluid-amount + fluid-viscosity (Phase 10 §5).
|
|
|
|
This component is the PANEL CONTENT hosted inside WaveformVisualizerControlPopover. It no longer rides
|
|
an inline TopRowCenter bar; it lays out as a wrapped grid sized for a popover, styled to the NowPlaying
|
|
Hero look (§3g — dark-navy ground, green-accent knobs, light icons) from the deepdrft-* tokens.
|
|
|
|
Because MudPopover PORTALS its content out of this component's DOM subtree, Blazor CSS isolation does
|
|
not reach the rendered panel — so panel chrome lives in the GLOBAL deepdrft-styles.css
|
|
(.waveform-visualizer-control-panel*), not in the scoped .razor.css. The scoped .razor.css carries only
|
|
the inline-bar fallback (the .mix-visualizer-controls-bar reserved-height row) Mix's existing
|
|
TopRowCenter mount still uses, which is NOT portaled and so resolves under isolation.
|
|
|
|
Visibility: the popover host always shows the panel when open (Visible defaults true). Mix's legacy
|
|
inline mount still feeds its lava-lamp toggle into Visible (Phase 10 §4): the knobs @if-gate while the
|
|
container holds a reserved min-height so content below never pops on toggle.
|
|
|
|
It owns NO JS interop: it mutates the shared, session-scoped WaveformVisualizerControlState and raises
|
|
its Changed event. The visualizer bridge (WaveformVisualizer) subscribes to that event and pushes the
|
|
affected dial to the WebGL module. That keeps the JS module handle single-owned by the bridge and this
|
|
component purely presentational. None of these is a seek surface (read-only contract §D).
|
|
|
|
RadialKnob has no icon slot (its Label renders as SVG text) and no aria attribute-capture, so each
|
|
control's Material icon rides beside its knob as an adjacent MudIcon caption and the accessible name
|
|
rides on the wrapping group div (§7d). HoldValue stays false so the knobs are live — ValueChanged fires
|
|
continuously during drag, preserving the Changed/NotifyChanged seam. *@
|
|
|
|
<div class="@($"{_panelChromeClass} mix-visualizer-controls-bar".TrimStart())">
|
|
|
|
@if (Visible)
|
|
{
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Waveform scroll speed">
|
|
<RadialKnob Value="@ControlState.ScrollSpeed"
|
|
ValueChanged="@OnScrollSpeedChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.Speed" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Color gradient rotation speed">
|
|
<RadialKnob Value="@ControlState.GradientRotationSpeed"
|
|
ValueChanged="@OnGradientRotationSpeedChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.Palette" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Lava gravity">
|
|
<RadialKnob Value="@ControlState.LavaGravity"
|
|
ValueChanged="@OnLavaGravityChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.ArrowDownward" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Lava heat">
|
|
<RadialKnob Value="@ControlState.LavaHeat"
|
|
ValueChanged="@OnLavaHeatChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.LocalFireDepartment" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Fluid amount">
|
|
<RadialKnob Value="@ControlState.FluidAmount"
|
|
ValueChanged="@OnFluidAmountChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.BubbleChart" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Fluid viscosity">
|
|
<RadialKnob Value="@ControlState.FluidViscosity"
|
|
ValueChanged="@OnFluidViscosityChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.Opacity" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Collision strength">
|
|
<RadialKnob Value="@ControlState.CollisionStrength"
|
|
ValueChanged="@OnCollisionStrengthChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.Compress" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
|
|
<div class="waveform-visualizer-control mix-visualizer-control" role="group" aria-label="Waveform width">
|
|
<RadialKnob Value="@ControlState.WaveformWidth"
|
|
ValueChanged="@OnWaveformWidthChanged"
|
|
Min="0" Max="1" Step="0.001"
|
|
Size="64"
|
|
Color="Color.Primary" />
|
|
<MudIcon Icon="@Icons.Material.Filled.SettingsEthernet" Size="Size.Small" Class="waveform-visualizer-control-icon mix-visualizer-control-icon" />
|
|
</div>
|
|
}
|
|
|
|
</div>
|
|
|
|
@code {
|
|
/// <summary>
|
|
/// Whether the knob band is shown. The popover host shows the panel whenever it is open, so the
|
|
/// default is <c>true</c>. Mix's legacy inline mount still feeds its lava-lamp toggle into this — that
|
|
/// mount always renders the component, and THIS component decides knob visibility (Phase 10 §4): when
|
|
/// false the knobs are @if-gated out but the container holds its reserved height (CSS min-height), so
|
|
/// content below the inline bar never pops as the lamp toggles. Inside the popover the host owns
|
|
/// open/closed, so the default keeps the panel populated.
|
|
/// </summary>
|
|
[Parameter] public bool Visible { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// When <c>true</c>, applies the <c>waveform-visualizer-control-panel</c> class to the root element,
|
|
/// enabling the global panel-chrome rules (dark-navy ground, border, max-width cap, pinned palette
|
|
/// tokens). Set by <see cref="WaveformVisualizerControlPopover"/>; Mix's inline mount leaves this
|
|
/// <c>false</c> so the chrome never leaks onto the inline bar.
|
|
/// </summary>
|
|
[Parameter] public bool PanelChrome { get; set; } = false;
|
|
|
|
private string _panelChromeClass => PanelChrome ? "waveform-visualizer-control-panel" : string.Empty;
|
|
|
|
// Each handler mutates its own dedicated property then raises Changed — the bridge re-reads and
|
|
// pushes the affected dial. All values are already normalized [0,1]; the bridge maps scroll speed
|
|
// to a visible time-span and routes the rest straight to the lava/colour dials.
|
|
|
|
private void OnScrollSpeedChanged(double value)
|
|
{
|
|
ControlState.ScrollSpeed = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnGradientRotationSpeedChanged(double value)
|
|
{
|
|
ControlState.GradientRotationSpeed = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnLavaGravityChanged(double value)
|
|
{
|
|
ControlState.LavaGravity = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnLavaHeatChanged(double value)
|
|
{
|
|
ControlState.LavaHeat = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnFluidAmountChanged(double value)
|
|
{
|
|
ControlState.FluidAmount = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnFluidViscosityChanged(double value)
|
|
{
|
|
ControlState.FluidViscosity = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnCollisionStrengthChanged(double value)
|
|
{
|
|
ControlState.CollisionStrength = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
|
|
private void OnWaveformWidthChanged(double value)
|
|
{
|
|
ControlState.WaveformWidth = value;
|
|
ControlState.NotifyChanged();
|
|
}
|
|
}
|