diff --git a/DeepDrftShared.Client/Components/RadialKnob.razor b/DeepDrftShared.Client/Components/RadialKnob.razor new file mode 100644 index 0000000..809878a --- /dev/null +++ b/DeepDrftShared.Client/Components/RadialKnob.razor @@ -0,0 +1,225 @@ +@using System.Globalization + + +@if (_isDragging) +{ +
+
+} + +
+ + + + + + + + + + + + + + + + + + @GetDisplayLabel() + + +
+ + + +@code { + [Parameter] public double Value { get; set; } + [Parameter] public EventCallback ValueChanged { get; set; } + [Parameter] public double Min { get; set; } = 0; + [Parameter] public double Max { get; set; } = 100; + [Parameter] public double Step { get; set; } = 1; + [Parameter] public string Label { get; set; } = ""; + [Parameter] public int Size { get; set; } = 50; + [Parameter] public MudBlazor.Color Color { get; set; } = MudBlazor.Color.Primary; + [Parameter] public bool HoldValue { get; set; } = false; + + private bool _isDragging = false; + private double _lastMouseY = 0; + private double _dragValue = 0; + + private double GetNormalizedValue() + { + // Use drag value during dragging if HoldValue is enabled, otherwise use current Value + double currentValue = (_isDragging && HoldValue) ? _dragValue : Value; + return Math.Max(0, Math.Min(1, (currentValue - Min) / (Max - Min))); + } + + private string GetDisplayLabel() + { + // Show drag value during dragging if HoldValue is enabled, otherwise use provided Label + if (_isDragging && HoldValue) + { + return _dragValue.ToString("F0"); + } + return Label; + } + + private string GetKnobColor() + { + return Color switch + { + MudBlazor.Color.Primary => "var(--mud-palette-primary)", + MudBlazor.Color.Secondary => "var(--mud-palette-secondary)", + MudBlazor.Color.Success => "var(--mud-palette-success)", + MudBlazor.Color.Warning => "var(--mud-palette-warning)", + MudBlazor.Color.Error => "var(--mud-palette-error)", + MudBlazor.Color.Info => "var(--mud-palette-info)", + _ => "var(--mud-palette-primary)" + }; + } + + private string GetBackgroundStrokeDashArray() + { + double circumference = 2 * Math.PI * 35; // radius = 35 + double maxArc = circumference * 0.75; // 270 degrees + return $"{maxArc} {circumference}"; + } + + private string GetStrokeDashArray() + { + double circumference = 2 * Math.PI * 35; // radius = 35 + double maxArc = circumference * 0.75; // 270 degrees + double valueArc = maxArc * GetNormalizedValue(); + return $"{valueArc} {circumference}"; + } + + private string GetStrokeDashOffset() + { + // No offset needed - arc should start from the beginning and grow + return "0"; + } + + private string GetPointerX() + { + double angle = -225 + (270 * GetNormalizedValue()); // -225 to +45 degrees (centered on vertical) + double radians = angle * Math.PI / 180; + double x = 50 + (25 * Math.Cos(radians)); // radius = 25 for pointer + return x.ToString("F1", CultureInfo.InvariantCulture); + } + + private string GetPointerY() + { + double angle = -225 + (270 * GetNormalizedValue()); + double radians = angle * Math.PI / 180; + double y = 50 + (25 * Math.Sin(radians)); + return y.ToString("F1", CultureInfo.InvariantCulture); + } + + private async Task OnMouseDown(MouseEventArgs e) + { + _isDragging = true; + _lastMouseY = e.ClientY; + _dragValue = Value; // Initialize drag value with current value + + // Add global mouse event handlers using Blazor's event handling + StateHasChanged(); + } + + private async Task OnGlobalMouseMove(MouseEventArgs e) + { + if (_isDragging) + { + await UpdateValueFromMouse(e); + } + } + + private async Task OnGlobalMouseUp(MouseEventArgs e) + { + if (_isDragging && HoldValue) + { + // If HoldValue is enabled, emit the final value now + if (Math.Abs(_dragValue - Value) > 0.001) + { + await ValueChanged.InvokeAsync(_dragValue); + } + } + + _isDragging = false; + StateHasChanged(); + } + + private async Task UpdateValueFromMouse(MouseEventArgs e) + { + // Calculate vertical delta from last mouse position + double deltaY = _lastMouseY - e.ClientY; // Inverted: up = positive, down = negative + _lastMouseY = e.ClientY; + + // Sensitivity factor (pixels to move for full range) + double sensitivity = 100.0; + + // Calculate change in normalized value based on vertical movement + double normalizedDelta = deltaY / sensitivity; + double currentNormalized = GetNormalizedValue(); + double newNormalized = Math.Max(0, Math.Min(1, currentNormalized + normalizedDelta)); + + // Convert to actual value + double newValue = Min + (newNormalized * (Max - Min)); + + // Apply step + if (Step > 0) + { + newValue = Math.Round(newValue / Step) * Step; + } + + if (Math.Abs(newValue - (HoldValue ? _dragValue : Value)) > 0.001) // Avoid unnecessary updates + { + if (HoldValue) + { + // Update drag value only, don't emit ValueChanged yet + _dragValue = newValue; + StateHasChanged(); // Update visual only + } + else + { + // Original behavior - emit ValueChanged immediately + Value = newValue; + await ValueChanged.InvokeAsync(Value); + } + } + } +} \ No newline at end of file