using System; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Media; namespace SlamCross.Controls; public sealed class WaveformControl : Control { private (float Low, float High)[] _peaks = Array.Empty<(float, float)>(); private double _startNorm; private double _endNorm = 1.0; private bool _dragging; private double _dragOriginNorm; private static readonly IBrush BgBrush = new SolidColorBrush(Color.FromRgb(18, 18, 28)); private static readonly IBrush SelBgBrush = new SolidColorBrush(Color.FromArgb(50, 80, 160, 255)); private static readonly IBrush WaveBrush = new SolidColorBrush(Color.FromRgb(40, 100, 180)); private static readonly IBrush SelWaveBrush = new SolidColorBrush(Color.FromRgb(100, 180, 255)); private static readonly IPen StartPen = new Pen(Brushes.LimeGreen, 2); private static readonly IPen EndPen = new Pen(Brushes.OrangeRed, 2); public event Action? SelectionChanged; public (double Start, double End) Selection => (_startNorm, _endNorm); public void SetPeaks((float Low, float High)[] peaks) { _peaks = peaks; InvalidateVisual(); } public void SetSelection(double startNorm, double endNorm) { _startNorm = Math.Clamp(startNorm, 0, 1); _endNorm = Math.Clamp(endNorm, 0, 1); InvalidateVisual(); } private double XToNorm(double x) => Math.Clamp(x / Bounds.Width, 0, 1); private double NormToX(double n) => n * Bounds.Width; public override void Render(DrawingContext ctx) { var w = Bounds.Width; var h = Bounds.Height; if (w <= 0 || h <= 0) return; ctx.FillRectangle(BgBrush, new Rect(0, 0, w, h)); if (_peaks.Length == 0) return; double midY = h / 2.0; double startX = NormToX(_startNorm); double endX = NormToX(_endNorm); double selW = Math.Max(1, endX - startX); ctx.FillRectangle(SelBgBrush, new Rect(startX, 0, selW, h)); int iw = (int)w; for (int px = 0; px < iw; px++) { int peakIdx = (int)((double)px / iw * _peaks.Length); if (peakIdx >= _peaks.Length) break; var (lo, hi) = _peaks[peakIdx]; double top = midY - hi * midY; double bot = midY - lo * midY; bool inSel = px >= startX - 0.5 && px <= endX + 0.5; ctx.FillRectangle(inSel ? SelWaveBrush : WaveBrush, new Rect(px, top, 1, Math.Max(1, bot - top))); } ctx.DrawLine(StartPen, new Point(startX, 0), new Point(startX, h)); ctx.DrawLine(EndPen, new Point(endX, 0), new Point(endX, h)); } protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); if (_peaks.Length == 0) return; _dragging = true; _dragOriginNorm = XToNorm(e.GetPosition(this).X); _startNorm = _dragOriginNorm; _endNorm = _dragOriginNorm; InvalidateVisual(); e.Pointer.Capture(this); e.Handled = true; } protected override void OnPointerMoved(PointerEventArgs e) { base.OnPointerMoved(e); if (!_dragging) return; double norm = XToNorm(e.GetPosition(this).X); _startNorm = Math.Min(norm, _dragOriginNorm); _endNorm = Math.Max(norm, _dragOriginNorm); InvalidateVisual(); } protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); if (!_dragging) return; _dragging = false; e.Pointer.Capture(null); SelectionChanged?.Invoke(_startNorm, _endNorm); e.Handled = true; } }