| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- 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<double, double>? 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;
- }
- }
|