PointerCanvas.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. e.PreventGestureRecognition();
  69. var currentPoint = e.GetCurrentPoint(v);
  70. if (e.RoutedEvent == PointerPressedEvent)
  71. AddPoint(currentPoint.Position, Brushes.Green, 10);
  72. else if (e.RoutedEvent == PointerReleasedEvent)
  73. AddPoint(currentPoint.Position, Brushes.Red, 10);
  74. else
  75. {
  76. var pts = e.GetIntermediatePoints(v);
  77. for (var c = 0; c < pts.Count; c++)
  78. {
  79. var pt = pts[c];
  80. AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black,
  81. c == pts.Count - 1 ? 5 : 2, pt.Properties.Pressure);
  82. }
  83. }
  84. }
  85. }
  86. private int _threadSleep;
  87. public static readonly DirectProperty<PointerCanvas, int> ThreadSleepProperty =
  88. AvaloniaProperty.RegisterDirect<PointerCanvas, int>(nameof(ThreadSleep), c => c.ThreadSleep, (c, v) => c.ThreadSleep = v);
  89. public int ThreadSleep
  90. {
  91. get => _threadSleep;
  92. set => SetAndRaise(ThreadSleepProperty, ref _threadSleep, value);
  93. }
  94. private bool _drawOnlyPoints;
  95. public static readonly DirectProperty<PointerCanvas, bool> DrawOnlyPointsProperty =
  96. AvaloniaProperty.RegisterDirect<PointerCanvas, bool>(nameof(DrawOnlyPoints), c => c.DrawOnlyPoints, (c, v) => c.DrawOnlyPoints = v);
  97. public bool DrawOnlyPoints
  98. {
  99. get => _drawOnlyPoints;
  100. set => SetAndRaise(DrawOnlyPointsProperty, ref _drawOnlyPoints, value);
  101. }
  102. private string? _status;
  103. public static readonly DirectProperty<PointerCanvas, string?> StatusProperty =
  104. AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(Status), c => c.Status, (c, v) => c.Status = v,
  105. defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
  106. public string? Status
  107. {
  108. get => _status;
  109. set => SetAndRaise(StatusProperty, ref _status, value);
  110. }
  111. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  112. {
  113. base.OnAttachedToVisualTree(e);
  114. _statusUpdated = DispatcherTimer.Run(() =>
  115. {
  116. if (_stopwatch.Elapsed.TotalMilliseconds > 250)
  117. {
  118. Status = $@"Events per second: {(_events / _stopwatch.Elapsed.TotalSeconds)}
  119. PointerUpdateKind: {_lastProperties?.PointerUpdateKind}
  120. Last PointerUpdateKind != Other: {_lastNonOtherUpdateKind}
  121. IsLeftButtonPressed: {_lastProperties?.IsLeftButtonPressed}
  122. IsRightButtonPressed: {_lastProperties?.IsRightButtonPressed}
  123. IsMiddleButtonPressed: {_lastProperties?.IsMiddleButtonPressed}
  124. IsXButton1Pressed: {_lastProperties?.IsXButton1Pressed}
  125. IsXButton2Pressed: {_lastProperties?.IsXButton2Pressed}
  126. IsBarrelButtonPressed: {_lastProperties?.IsBarrelButtonPressed}
  127. IsEraser: {_lastProperties?.IsEraser}
  128. IsInverted: {_lastProperties?.IsInverted}
  129. Pressure: {_lastProperties?.Pressure}
  130. XTilt: {_lastProperties?.XTilt}
  131. YTilt: {_lastProperties?.YTilt}
  132. Twist: {_lastProperties?.Twist}";
  133. _stopwatch.Restart();
  134. _events = 0;
  135. }
  136. return true;
  137. }, TimeSpan.FromMilliseconds(10));
  138. }
  139. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  140. {
  141. base.OnDetachedFromVisualTree(e);
  142. _statusUpdated?.Dispose();
  143. }
  144. void HandleEvent(PointerEventArgs e)
  145. {
  146. _events++;
  147. if (_threadSleep != 0)
  148. {
  149. Thread.Sleep(_threadSleep);
  150. }
  151. InvalidateVisual();
  152. var lastPointer = e.GetCurrentPoint(this);
  153. _lastProperties = lastPointer.Properties;
  154. if (_lastProperties?.PointerUpdateKind != PointerUpdateKind.Other)
  155. {
  156. _lastNonOtherUpdateKind = _lastProperties?.PointerUpdateKind;
  157. }
  158. if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)
  159. {
  160. _pointers.Remove(e.Pointer.Id);
  161. return;
  162. }
  163. if (e.Pointer.Type != PointerType.Pen
  164. || lastPointer.Properties.Pressure > 0)
  165. {
  166. if (!_pointers.TryGetValue(e.Pointer.Id, out var pt))
  167. _pointers[e.Pointer.Id] = pt = new PointerPoints();
  168. pt.HandleEvent(e, this);
  169. }
  170. }
  171. public override void Render(DrawingContext context)
  172. {
  173. context.FillRectangle(Brushes.White, Bounds);
  174. foreach (var pt in _pointers.Values)
  175. pt.Render(context, _drawOnlyPoints);
  176. base.Render(context);
  177. }
  178. protected override void OnPointerPressed(PointerPressedEventArgs e)
  179. {
  180. if (e.ClickCount == 2)
  181. {
  182. _pointers.Clear();
  183. InvalidateVisual();
  184. return;
  185. }
  186. HandleEvent(e);
  187. base.OnPointerPressed(e);
  188. }
  189. protected override void OnPointerMoved(PointerEventArgs e)
  190. {
  191. HandleEvent(e);
  192. base.OnPointerMoved(e);
  193. }
  194. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  195. {
  196. HandleEvent(e);
  197. base.OnPointerReleased(e);
  198. }
  199. protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
  200. {
  201. _lastProperties = null;
  202. base.OnPointerCaptureLost(e);
  203. }
  204. }