WaveformControl.cs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. using System;
  2. using Avalonia;
  3. using Avalonia.Controls;
  4. using Avalonia.Input;
  5. using Avalonia.Media;
  6. namespace SlamCross.Controls;
  7. public sealed class WaveformControl : Control
  8. {
  9. private (float Low, float High)[] _peaks = Array.Empty<(float, float)>();
  10. private double _startNorm;
  11. private double _endNorm = 1.0;
  12. private bool _dragging;
  13. private double _dragOriginNorm;
  14. private static readonly IBrush BgBrush = new SolidColorBrush(Color.FromRgb(18, 18, 28));
  15. private static readonly IBrush SelBgBrush = new SolidColorBrush(Color.FromArgb(50, 80, 160, 255));
  16. private static readonly IBrush WaveBrush = new SolidColorBrush(Color.FromRgb(40, 100, 180));
  17. private static readonly IBrush SelWaveBrush = new SolidColorBrush(Color.FromRgb(100, 180, 255));
  18. private static readonly IPen StartPen = new Pen(Brushes.LimeGreen, 2);
  19. private static readonly IPen EndPen = new Pen(Brushes.OrangeRed, 2);
  20. public event Action<double, double>? SelectionChanged;
  21. public (double Start, double End) Selection => (_startNorm, _endNorm);
  22. public void SetPeaks((float Low, float High)[] peaks)
  23. {
  24. _peaks = peaks;
  25. InvalidateVisual();
  26. }
  27. public void SetSelection(double startNorm, double endNorm)
  28. {
  29. _startNorm = Math.Clamp(startNorm, 0, 1);
  30. _endNorm = Math.Clamp(endNorm, 0, 1);
  31. InvalidateVisual();
  32. }
  33. private double XToNorm(double x) => Math.Clamp(x / Bounds.Width, 0, 1);
  34. private double NormToX(double n) => n * Bounds.Width;
  35. public override void Render(DrawingContext ctx)
  36. {
  37. var w = Bounds.Width;
  38. var h = Bounds.Height;
  39. if (w <= 0 || h <= 0) return;
  40. ctx.FillRectangle(BgBrush, new Rect(0, 0, w, h));
  41. if (_peaks.Length == 0) return;
  42. double midY = h / 2.0;
  43. double startX = NormToX(_startNorm);
  44. double endX = NormToX(_endNorm);
  45. double selW = Math.Max(1, endX - startX);
  46. ctx.FillRectangle(SelBgBrush, new Rect(startX, 0, selW, h));
  47. int iw = (int)w;
  48. for (int px = 0; px < iw; px++)
  49. {
  50. int peakIdx = (int)((double)px / iw * _peaks.Length);
  51. if (peakIdx >= _peaks.Length) break;
  52. var (lo, hi) = _peaks[peakIdx];
  53. double top = midY - hi * midY;
  54. double bot = midY - lo * midY;
  55. bool inSel = px >= startX - 0.5 && px <= endX + 0.5;
  56. ctx.FillRectangle(inSel ? SelWaveBrush : WaveBrush,
  57. new Rect(px, top, 1, Math.Max(1, bot - top)));
  58. }
  59. ctx.DrawLine(StartPen, new Point(startX, 0), new Point(startX, h));
  60. ctx.DrawLine(EndPen, new Point(endX, 0), new Point(endX, h));
  61. }
  62. protected override void OnPointerPressed(PointerPressedEventArgs e)
  63. {
  64. base.OnPointerPressed(e);
  65. if (_peaks.Length == 0) return;
  66. _dragging = true;
  67. _dragOriginNorm = XToNorm(e.GetPosition(this).X);
  68. _startNorm = _dragOriginNorm;
  69. _endNorm = _dragOriginNorm;
  70. InvalidateVisual();
  71. e.Pointer.Capture(this);
  72. e.Handled = true;
  73. }
  74. protected override void OnPointerMoved(PointerEventArgs e)
  75. {
  76. base.OnPointerMoved(e);
  77. if (!_dragging) return;
  78. double norm = XToNorm(e.GetPosition(this).X);
  79. _startNorm = Math.Min(norm, _dragOriginNorm);
  80. _endNorm = Math.Max(norm, _dragOriginNorm);
  81. InvalidateVisual();
  82. }
  83. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  84. {
  85. base.OnPointerReleased(e);
  86. if (!_dragging) return;
  87. _dragging = false;
  88. e.Pointer.Capture(null);
  89. SelectionChanged?.Invoke(_startNorm, _endNorm);
  90. e.Handled = true;
  91. }
  92. }