PointerCanvas.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Threading;
  6. using Avalonia;
  7. using Avalonia.Controls;
  8. using Avalonia.Input;
  9. using Avalonia.Media;
  10. using Avalonia.Threading;
  11. namespace ControlCatalog.Pages;
  12. public class PointerCanvas : Control
  13. {
  14. private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
  15. private int _events;
  16. private IDisposable? _statusUpdated;
  17. private Dictionary<int, PointerPoints> _pointers = new();
  18. private PointerPointProperties? _lastProperties;
  19. private PointerUpdateKind? _lastNonOtherUpdateKind;
  20. class PointerPoints
  21. {
  22. struct CanvasPoint
  23. {
  24. public IBrush? Brush;
  25. public Point Point;
  26. public double Radius;
  27. public double? Pressure;
  28. }
  29. readonly CanvasPoint[] _points = new CanvasPoint[1000];
  30. int _index;
  31. public void Render(DrawingContext context, bool drawPoints)
  32. {
  33. CanvasPoint? prev = null;
  34. for (var c = 0; c < _points.Length; c++)
  35. {
  36. var i = (c + _index) % _points.Length;
  37. var pt = _points[i];
  38. var pressure = (pt.Pressure ?? prev?.Pressure ?? 0.5);
  39. var thickness = pressure * 10;
  40. var radius = pressure * pt.Radius;
  41. if (drawPoints)
  42. {
  43. if (pt.Brush != null)
  44. {
  45. context.DrawEllipse(pt.Brush, null, pt.Point, radius, radius);
  46. }
  47. }
  48. else
  49. {
  50. if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null
  51. && prev.Value.Pressure != null && pt.Pressure != null)
  52. {
  53. var linePen = new Pen(Brushes.Black, thickness, null, PenLineCap.Round, PenLineJoin.Round);
  54. context.DrawLine(linePen, prev.Value.Point, pt.Point);
  55. }
  56. }
  57. prev = pt;
  58. }
  59. }
  60. void AddPoint(Point pt, IBrush brush, double radius, float? pressure = null)
  61. {
  62. _points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius, Pressure = pressure };
  63. _index = (_index + 1) % _points.Length;
  64. }
  65. public void HandleEvent(PointerEventArgs e, Visual v)
  66. {
  67. e.Handled = true;
  68. var currentPoint = e.GetCurrentPoint(v);
  69. if (e.RoutedEvent == PointerPressedEvent)
  70. AddPoint(currentPoint.Position, Brushes.Green, 10);
  71. else if (e.RoutedEvent == PointerReleasedEvent)
  72. AddPoint(currentPoint.Position, Brushes.Red, 10);
  73. else
  74. {
  75. var pts = e.GetIntermediatePoints(v);
  76. for (var c = 0; c < pts.Count; c++)
  77. {
  78. var pt = pts[c];
  79. AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black,
  80. c == pts.Count - 1 ? 5 : 2, pt.Properties.Pressure);
  81. }
  82. }
  83. }
  84. }
  85. private int _threadSleep;
  86. public static DirectProperty<PointerCanvas, int> ThreadSleepProperty =
  87. AvaloniaProperty.RegisterDirect<PointerCanvas, int>(nameof(ThreadSleep), c => c.ThreadSleep, (c, v) => c.ThreadSleep = v);
  88. public int ThreadSleep
  89. {
  90. get => _threadSleep;
  91. set => SetAndRaise(ThreadSleepProperty, ref _threadSleep, value);
  92. }
  93. private bool _drawOnlyPoints;
  94. public static DirectProperty<PointerCanvas, bool> DrawOnlyPointsProperty =
  95. AvaloniaProperty.RegisterDirect<PointerCanvas, bool>(nameof(DrawOnlyPoints), c => c.DrawOnlyPoints, (c, v) => c.DrawOnlyPoints = v);
  96. public bool DrawOnlyPoints
  97. {
  98. get => _drawOnlyPoints;
  99. set => SetAndRaise(DrawOnlyPointsProperty, ref _drawOnlyPoints, value);
  100. }
  101. private string? _status;
  102. public static DirectProperty<PointerCanvas, string?> StatusProperty =
  103. AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(DrawOnlyPoints), c => c.Status, (c, v) => c.Status = v,
  104. defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
  105. public string? Status
  106. {
  107. get => _status;
  108. set => SetAndRaise(StatusProperty, ref _status, value);
  109. }
  110. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  111. {
  112. base.OnAttachedToVisualTree(e);
  113. _statusUpdated = DispatcherTimer.Run(() =>
  114. {
  115. if (_stopwatch.Elapsed.TotalMilliseconds > 250)
  116. {
  117. Status = $@"Events per second: {(_events / _stopwatch.Elapsed.TotalSeconds)}
  118. PointerUpdateKind: {_lastProperties?.PointerUpdateKind}
  119. Last PointerUpdateKind != Other: {_lastNonOtherUpdateKind}
  120. IsLeftButtonPressed: {_lastProperties?.IsLeftButtonPressed}
  121. IsRightButtonPressed: {_lastProperties?.IsRightButtonPressed}
  122. IsMiddleButtonPressed: {_lastProperties?.IsMiddleButtonPressed}
  123. IsXButton1Pressed: {_lastProperties?.IsXButton1Pressed}
  124. IsXButton2Pressed: {_lastProperties?.IsXButton2Pressed}
  125. IsBarrelButtonPressed: {_lastProperties?.IsBarrelButtonPressed}
  126. IsEraser: {_lastProperties?.IsEraser}
  127. IsInverted: {_lastProperties?.IsInverted}
  128. Pressure: {_lastProperties?.Pressure}
  129. XTilt: {_lastProperties?.XTilt}
  130. YTilt: {_lastProperties?.YTilt}
  131. Twist: {_lastProperties?.Twist}";
  132. _stopwatch.Restart();
  133. _events = 0;
  134. }
  135. return true;
  136. }, TimeSpan.FromMilliseconds(10));
  137. }
  138. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  139. {
  140. base.OnDetachedFromVisualTree(e);
  141. _statusUpdated?.Dispose();
  142. }
  143. void HandleEvent(PointerEventArgs e)
  144. {
  145. _events++;
  146. if (_threadSleep != 0)
  147. {
  148. Thread.Sleep(_threadSleep);
  149. }
  150. InvalidateVisual();
  151. var lastPointer = e.GetCurrentPoint(this);
  152. _lastProperties = lastPointer.Properties;
  153. if (_lastProperties?.PointerUpdateKind != PointerUpdateKind.Other)
  154. {
  155. _lastNonOtherUpdateKind = _lastProperties?.PointerUpdateKind;
  156. }
  157. if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)
  158. {
  159. _pointers.Remove(e.Pointer.Id);
  160. return;
  161. }
  162. if (e.Pointer.Type != PointerType.Pen
  163. || lastPointer.Properties.Pressure > 0)
  164. {
  165. if (!_pointers.TryGetValue(e.Pointer.Id, out var pt))
  166. _pointers[e.Pointer.Id] = pt = new PointerPoints();
  167. pt.HandleEvent(e, this);
  168. }
  169. }
  170. public override void Render(DrawingContext context)
  171. {
  172. context.FillRectangle(Brushes.White, Bounds);
  173. foreach (var pt in _pointers.Values)
  174. pt.Render(context, _drawOnlyPoints);
  175. base.Render(context);
  176. }
  177. protected override void OnPointerPressed(PointerPressedEventArgs e)
  178. {
  179. if (e.ClickCount == 2)
  180. {
  181. _pointers.Clear();
  182. InvalidateVisual();
  183. return;
  184. }
  185. HandleEvent(e);
  186. base.OnPointerPressed(e);
  187. }
  188. protected override void OnPointerMoved(PointerEventArgs e)
  189. {
  190. HandleEvent(e);
  191. base.OnPointerMoved(e);
  192. }
  193. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  194. {
  195. HandleEvent(e);
  196. base.OnPointerReleased(e);
  197. }
  198. protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
  199. {
  200. _lastProperties = null;
  201. base.OnPointerCaptureLost(e);
  202. }
  203. }