PointersPage.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reactive.Linq;
  7. using System.Runtime.InteropServices;
  8. using System.Threading;
  9. using Avalonia;
  10. using Avalonia.Controls;
  11. using Avalonia.Controls.Documents;
  12. using Avalonia.Input;
  13. using Avalonia.Layout;
  14. using Avalonia.Media;
  15. using Avalonia.Media.Immutable;
  16. using Avalonia.Threading;
  17. using Avalonia.VisualTree;
  18. namespace ControlCatalog.Pages;
  19. public class PointersPage : Decorator
  20. {
  21. public PointersPage()
  22. {
  23. Child = new TabControl
  24. {
  25. Items = new[]
  26. {
  27. new TabItem() { Header = "Contacts", Content = new PointerContactsTab() },
  28. new TabItem() { Header = "IntermediatePoints", Content = new PointerIntermediatePointsTab() },
  29. new TabItem() { Header = "Pressure", Content = new PointerPressureTab() }
  30. }
  31. };
  32. }
  33. class PointerContactsTab : Control
  34. {
  35. class PointerInfo
  36. {
  37. public Point Point { get; set; }
  38. public Color Color { get; set; }
  39. }
  40. private static Color[] AllColors = new[]
  41. {
  42. Colors.Aqua,
  43. Colors.Beige,
  44. Colors.Chartreuse,
  45. Colors.Coral,
  46. Colors.Fuchsia,
  47. Colors.Crimson,
  48. Colors.Lavender,
  49. Colors.Orange,
  50. Colors.Orchid,
  51. Colors.ForestGreen,
  52. Colors.SteelBlue,
  53. Colors.PapayaWhip,
  54. Colors.PaleVioletRed,
  55. Colors.Goldenrod,
  56. Colors.Maroon,
  57. Colors.Moccasin,
  58. Colors.Navy,
  59. Colors.Wheat,
  60. Colors.Violet,
  61. Colors.Sienna,
  62. Colors.Indigo,
  63. Colors.Honeydew
  64. };
  65. private Dictionary<IPointer, PointerInfo> _pointers = new Dictionary<IPointer, PointerInfo>();
  66. public PointerContactsTab()
  67. {
  68. ClipToBounds = true;
  69. }
  70. void UpdatePointer(PointerEventArgs e)
  71. {
  72. if (!_pointers.TryGetValue(e.Pointer, out var info))
  73. {
  74. if (e.RoutedEvent == PointerMovedEvent)
  75. return;
  76. var colors = AllColors.Except(_pointers.Values.Select(c => c.Color)).ToArray();
  77. var color = colors[new Random().Next(0, colors.Length - 1)];
  78. _pointers[e.Pointer] = info = new PointerInfo {Color = color};
  79. }
  80. info.Point = e.GetPosition(this);
  81. InvalidateVisual();
  82. }
  83. protected override void OnPointerPressed(PointerPressedEventArgs e)
  84. {
  85. UpdatePointer(e);
  86. e.Pointer.Capture(this);
  87. e.Handled = true;
  88. base.OnPointerPressed(e);
  89. }
  90. protected override void OnPointerMoved(PointerEventArgs e)
  91. {
  92. UpdatePointer(e);
  93. e.Handled = true;
  94. base.OnPointerMoved(e);
  95. }
  96. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  97. {
  98. _pointers.Remove(e.Pointer);
  99. e.Handled = true;
  100. InvalidateVisual();
  101. }
  102. protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
  103. {
  104. _pointers.Remove(e.Pointer);
  105. InvalidateVisual();
  106. }
  107. public override void Render(DrawingContext context)
  108. {
  109. context.FillRectangle(Brushes.Transparent, new Rect(default, Bounds.Size));
  110. foreach (var pt in _pointers.Values)
  111. {
  112. var brush = new ImmutableSolidColorBrush(pt.Color);
  113. context.DrawEllipse(brush, null, pt.Point, 75, 75);
  114. }
  115. }
  116. }
  117. public class PointerIntermediatePointsTab : Decorator
  118. {
  119. public PointerIntermediatePointsTab()
  120. {
  121. this[TextElement.ForegroundProperty] = Brushes.Black;
  122. var slider = new Slider
  123. {
  124. Margin = new Thickness(5),
  125. Minimum = 0,
  126. Maximum = 500
  127. };
  128. var status = new TextBlock()
  129. {
  130. HorizontalAlignment = HorizontalAlignment.Left,
  131. VerticalAlignment = VerticalAlignment.Top,
  132. };
  133. Child = new Grid
  134. {
  135. Children =
  136. {
  137. new PointerCanvas(slider, status, true),
  138. new Border
  139. {
  140. Background = Brushes.LightYellow,
  141. Child = new StackPanel
  142. {
  143. Children =
  144. {
  145. new StackPanel
  146. {
  147. Orientation = Orientation.Horizontal,
  148. Children =
  149. {
  150. new TextBlock { Text = "Thread sleep:" },
  151. new TextBlock()
  152. {
  153. [!TextBlock.TextProperty] =slider.GetObservable(Slider.ValueProperty)
  154. .Select(x=>x.ToString()).ToBinding()
  155. }
  156. }
  157. },
  158. slider
  159. }
  160. },
  161. HorizontalAlignment = HorizontalAlignment.Right,
  162. VerticalAlignment = VerticalAlignment.Top,
  163. Width = 300,
  164. Height = 60
  165. },
  166. status
  167. }
  168. };
  169. }
  170. }
  171. public class PointerPressureTab : Decorator
  172. {
  173. public PointerPressureTab()
  174. {
  175. this[TextBlock.ForegroundProperty] = Brushes.Black;
  176. var status = new TextBlock()
  177. {
  178. HorizontalAlignment = HorizontalAlignment.Left,
  179. VerticalAlignment = VerticalAlignment.Top,
  180. FontSize = 12
  181. };
  182. Child = new Grid
  183. {
  184. Children =
  185. {
  186. new PointerCanvas(null, status, false),
  187. status
  188. }
  189. };
  190. }
  191. }
  192. class PointerCanvas : Control
  193. {
  194. private readonly Slider? _slider;
  195. private readonly TextBlock _status;
  196. private readonly bool _drawPoints;
  197. private int _events;
  198. private Stopwatch _stopwatch = Stopwatch.StartNew();
  199. private IDisposable? _statusUpdated;
  200. private Dictionary<int, PointerPoints> _pointers = new();
  201. private PointerPointProperties? _lastProperties;
  202. class PointerPoints
  203. {
  204. struct CanvasPoint
  205. {
  206. public IBrush Brush;
  207. public Point Point;
  208. public double Radius;
  209. public double Pressure;
  210. }
  211. readonly CanvasPoint[] _points = new CanvasPoint[1000];
  212. int _index;
  213. public void Render(DrawingContext context, bool drawPoints)
  214. {
  215. CanvasPoint? prev = null;
  216. for (var c = 0; c < _points.Length; c++)
  217. {
  218. var i = (c + _index) % _points.Length;
  219. var pt = _points[i];
  220. var thickness = pt.Pressure == 0 ? 1 : (pt.Pressure / 1024) * 5;
  221. if (drawPoints)
  222. {
  223. if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null)
  224. context.DrawLine(new Pen(Brushes.Black, thickness), prev.Value.Point, pt.Point);
  225. if (pt.Brush != null)
  226. context.DrawEllipse(pt.Brush, null, pt.Point, pt.Radius, pt.Radius);
  227. }
  228. else
  229. {
  230. if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null)
  231. context.DrawLine(new Pen(Brushes.Black, thickness, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round), prev.Value.Point, pt.Point);
  232. }
  233. prev = pt;
  234. }
  235. }
  236. void AddPoint(Point pt, IBrush brush, double radius, float pressure)
  237. {
  238. _points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius, Pressure = pressure };
  239. _index = (_index + 1) % _points.Length;
  240. }
  241. public void HandleEvent(PointerEventArgs e, Visual v)
  242. {
  243. e.Handled = true;
  244. var currentPoint = e.GetCurrentPoint(v);
  245. if (e.RoutedEvent == PointerPressedEvent)
  246. AddPoint(currentPoint.Position, Brushes.Green, 10, currentPoint.Properties.Pressure);
  247. else if (e.RoutedEvent == PointerReleasedEvent)
  248. AddPoint(currentPoint.Position, Brushes.Red, 10, currentPoint.Properties.Pressure);
  249. else
  250. {
  251. var pts = e.GetIntermediatePoints(v);
  252. for (var c = 0; c < pts.Count; c++)
  253. {
  254. var pt = pts[c];
  255. AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black,
  256. c == pts.Count - 1 ? 5 : 2, pt.Properties.Pressure);
  257. }
  258. }
  259. }
  260. }
  261. public PointerCanvas(Slider? slider, TextBlock status, bool drawPoints)
  262. {
  263. _slider = slider;
  264. _status = status;
  265. _drawPoints = drawPoints;
  266. }
  267. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  268. {
  269. base.OnAttachedToVisualTree(e);
  270. _statusUpdated = DispatcherTimer.Run(() =>
  271. {
  272. if (_stopwatch.Elapsed.TotalSeconds > 1)
  273. {
  274. _status.Text = $@"Events per second: {(_events / _stopwatch.Elapsed.TotalSeconds)}
  275. PointerUpdateKind: {_lastProperties?.PointerUpdateKind}
  276. IsLeftButtonPressed: {_lastProperties?.IsLeftButtonPressed}
  277. IsRightButtonPressed: {_lastProperties?.IsRightButtonPressed}
  278. IsMiddleButtonPressed: {_lastProperties?.IsMiddleButtonPressed}
  279. IsXButton1Pressed: {_lastProperties?.IsXButton1Pressed}
  280. IsXButton2Pressed: {_lastProperties?.IsXButton2Pressed}
  281. IsBarrelButtonPressed: {_lastProperties?.IsBarrelButtonPressed}
  282. IsEraser: {_lastProperties?.IsEraser}
  283. IsInverted: {_lastProperties?.IsInverted}
  284. Pressure: {_lastProperties?.Pressure}
  285. XTilt: {_lastProperties?.XTilt}
  286. YTilt: {_lastProperties?.YTilt}
  287. Twist: {_lastProperties?.Twist}";
  288. _stopwatch.Restart();
  289. _events = 0;
  290. }
  291. return true;
  292. }, TimeSpan.FromMilliseconds(10));
  293. }
  294. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  295. {
  296. base.OnDetachedFromVisualTree(e);
  297. _statusUpdated?.Dispose();
  298. }
  299. void HandleEvent(PointerEventArgs e)
  300. {
  301. _events++;
  302. if (_slider != null)
  303. {
  304. Thread.Sleep((int)_slider.Value);
  305. }
  306. InvalidateVisual();
  307. if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch)
  308. {
  309. _pointers.Remove(e.Pointer.Id);
  310. return;
  311. }
  312. var lastPointer = e.GetCurrentPoint(this);
  313. _lastProperties = lastPointer.Properties;
  314. if (e.Pointer.Type != PointerType.Pen
  315. || lastPointer.Properties.Pressure > 0)
  316. {
  317. if (!_pointers.TryGetValue(e.Pointer.Id, out var pt))
  318. _pointers[e.Pointer.Id] = pt = new PointerPoints();
  319. pt.HandleEvent(e, this);
  320. }
  321. }
  322. public override void Render(DrawingContext context)
  323. {
  324. context.FillRectangle(Brushes.White, Bounds);
  325. foreach (var pt in _pointers.Values)
  326. pt.Render(context, _drawPoints);
  327. base.Render(context);
  328. }
  329. protected override void OnPointerPressed(PointerPressedEventArgs e)
  330. {
  331. if (e.ClickCount == 2)
  332. {
  333. _pointers.Clear();
  334. InvalidateVisual();
  335. return;
  336. }
  337. HandleEvent(e);
  338. base.OnPointerPressed(e);
  339. }
  340. protected override void OnPointerMoved(PointerEventArgs e)
  341. {
  342. HandleEvent(e);
  343. base.OnPointerMoved(e);
  344. }
  345. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  346. {
  347. HandleEvent(e);
  348. base.OnPointerReleased(e);
  349. }
  350. }
  351. }