MouseDevice.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. using System;
  2. using System.Linq;
  3. using System.Reactive.Linq;
  4. using Avalonia.Input.Raw;
  5. using Avalonia.Interactivity;
  6. using Avalonia.Platform;
  7. using Avalonia.VisualTree;
  8. namespace Avalonia.Input
  9. {
  10. /// <summary>
  11. /// Represents a mouse device.
  12. /// </summary>
  13. public class MouseDevice : IMouseDevice, IDisposable
  14. {
  15. private int _clickCount;
  16. private Rect _lastClickRect;
  17. private ulong _lastClickTime;
  18. private readonly Pointer _pointer;
  19. private bool _disposed;
  20. private PixelPoint? _position;
  21. public MouseDevice(Pointer? pointer = null)
  22. {
  23. _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
  24. }
  25. /// <summary>
  26. /// Gets the control that is currently capturing by the mouse, if any.
  27. /// </summary>
  28. /// <remarks>
  29. /// When an element captures the mouse, it receives mouse input whether the cursor is
  30. /// within the control's bounds or not. To set the mouse capture, call the
  31. /// <see cref="Capture"/> method.
  32. /// </remarks>
  33. [Obsolete("Use IPointer instead")]
  34. public IInputElement? Captured => _pointer.Captured;
  35. /// <summary>
  36. /// Gets the mouse position, in screen coordinates.
  37. /// </summary>
  38. [Obsolete("Use events instead")]
  39. public PixelPoint Position
  40. {
  41. get => _position ?? new PixelPoint(-1, -1);
  42. protected set => _position = value;
  43. }
  44. /// <summary>
  45. /// Captures mouse input to the specified control.
  46. /// </summary>
  47. /// <param name="control">The control.</param>
  48. /// <remarks>
  49. /// When an element captures the mouse, it receives mouse input whether the cursor is
  50. /// within the control's bounds or not. The current mouse capture control is exposed
  51. /// by the <see cref="Captured"/> property.
  52. /// </remarks>
  53. public void Capture(IInputElement? control)
  54. {
  55. _pointer.Capture(control);
  56. }
  57. /// <summary>
  58. /// Gets the mouse position relative to a control.
  59. /// </summary>
  60. /// <param name="relativeTo">The control.</param>
  61. /// <returns>The mouse position in the control's coordinates.</returns>
  62. public Point GetPosition(IVisual relativeTo)
  63. {
  64. relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo));
  65. if (relativeTo.VisualRoot == null)
  66. {
  67. throw new InvalidOperationException("Control is not attached to visual tree.");
  68. }
  69. var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
  70. var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
  71. return rootPoint * transform!.Value;
  72. }
  73. public void ProcessRawEvent(RawInputEventArgs e)
  74. {
  75. if (!e.Handled && e is RawPointerEventArgs margs)
  76. ProcessRawEvent(margs);
  77. }
  78. public void TopLevelClosed(IInputRoot root)
  79. {
  80. ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None);
  81. }
  82. public void SceneInvalidated(IInputRoot root, Rect rect)
  83. {
  84. // Pointer is outside of the target area
  85. if (_position == null )
  86. {
  87. if (root.PointerOverElement != null)
  88. ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None);
  89. return;
  90. }
  91. var clientPoint = root.PointToClient(_position.Value);
  92. if (rect.Contains(clientPoint))
  93. {
  94. if (_pointer.Captured == null)
  95. {
  96. SetPointerOver(this, 0 /* TODO: proper timestamp */, root, clientPoint,
  97. PointerPointProperties.None, KeyModifiers.None);
  98. }
  99. else
  100. {
  101. SetPointerOver(this, 0 /* TODO: proper timestamp */, root, _pointer.Captured,
  102. PointerPointProperties.None, KeyModifiers.None);
  103. }
  104. }
  105. }
  106. int ButtonCount(PointerPointProperties props)
  107. {
  108. var rv = 0;
  109. if (props.IsLeftButtonPressed)
  110. rv++;
  111. if (props.IsMiddleButtonPressed)
  112. rv++;
  113. if (props.IsRightButtonPressed)
  114. rv++;
  115. if (props.IsXButton1Pressed)
  116. rv++;
  117. if (props.IsXButton2Pressed)
  118. rv++;
  119. return rv;
  120. }
  121. private void ProcessRawEvent(RawPointerEventArgs e)
  122. {
  123. e = e ?? throw new ArgumentNullException(nameof(e));
  124. var mouse = (MouseDevice)e.Device;
  125. if(mouse._disposed)
  126. return;
  127. _position = e.Root.PointToScreen(e.Position);
  128. var props = CreateProperties(e);
  129. var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers);
  130. switch (e.Type)
  131. {
  132. case RawPointerEventType.LeaveWindow:
  133. LeaveWindow(mouse, e.Timestamp, e.Root, props, keyModifiers);
  134. break;
  135. case RawPointerEventType.LeftButtonDown:
  136. case RawPointerEventType.RightButtonDown:
  137. case RawPointerEventType.MiddleButtonDown:
  138. case RawPointerEventType.XButton1Down:
  139. case RawPointerEventType.XButton2Down:
  140. if (ButtonCount(props) > 1)
  141. e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
  142. else
  143. e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
  144. props, keyModifiers);
  145. break;
  146. case RawPointerEventType.LeftButtonUp:
  147. case RawPointerEventType.RightButtonUp:
  148. case RawPointerEventType.MiddleButtonUp:
  149. case RawPointerEventType.XButton1Up:
  150. case RawPointerEventType.XButton2Up:
  151. if (ButtonCount(props) != 0)
  152. e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
  153. else
  154. e.Handled = MouseUp(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
  155. break;
  156. case RawPointerEventType.Move:
  157. e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
  158. break;
  159. case RawPointerEventType.Wheel:
  160. e.Handled = MouseWheel(mouse, e.Timestamp, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, keyModifiers);
  161. break;
  162. }
  163. }
  164. private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties,
  165. KeyModifiers inputModifiers)
  166. {
  167. device = device ?? throw new ArgumentNullException(nameof(device));
  168. root = root ?? throw new ArgumentNullException(nameof(root));
  169. _position = null;
  170. ClearPointerOver(this, timestamp, root, properties, inputModifiers);
  171. }
  172. PointerPointProperties CreateProperties(RawPointerEventArgs args)
  173. {
  174. var kind = PointerUpdateKind.Other;
  175. if (args.Type == RawPointerEventType.LeftButtonDown)
  176. kind = PointerUpdateKind.LeftButtonPressed;
  177. if (args.Type == RawPointerEventType.MiddleButtonDown)
  178. kind = PointerUpdateKind.MiddleButtonPressed;
  179. if (args.Type == RawPointerEventType.RightButtonDown)
  180. kind = PointerUpdateKind.RightButtonPressed;
  181. if (args.Type == RawPointerEventType.XButton1Down)
  182. kind = PointerUpdateKind.XButton1Pressed;
  183. if (args.Type == RawPointerEventType.XButton2Down)
  184. kind = PointerUpdateKind.XButton2Pressed;
  185. if (args.Type == RawPointerEventType.LeftButtonUp)
  186. kind = PointerUpdateKind.LeftButtonReleased;
  187. if (args.Type == RawPointerEventType.MiddleButtonUp)
  188. kind = PointerUpdateKind.MiddleButtonReleased;
  189. if (args.Type == RawPointerEventType.RightButtonUp)
  190. kind = PointerUpdateKind.RightButtonReleased;
  191. if (args.Type == RawPointerEventType.XButton1Up)
  192. kind = PointerUpdateKind.XButton1Released;
  193. if (args.Type == RawPointerEventType.XButton2Up)
  194. kind = PointerUpdateKind.XButton2Released;
  195. return new PointerPointProperties(args.InputModifiers, kind);
  196. }
  197. private MouseButton _lastMouseDownButton;
  198. private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
  199. PointerPointProperties properties,
  200. KeyModifiers inputModifiers)
  201. {
  202. device = device ?? throw new ArgumentNullException(nameof(device));
  203. root = root ?? throw new ArgumentNullException(nameof(root));
  204. var hit = HitTest(root, p);
  205. if (hit != null)
  206. {
  207. _pointer.Capture(hit);
  208. var source = GetSource(hit);
  209. if (source != null)
  210. {
  211. var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
  212. var doubleClickTime = settings.DoubleClickTime.TotalMilliseconds;
  213. if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
  214. {
  215. _clickCount = 0;
  216. }
  217. ++_clickCount;
  218. _lastClickTime = timestamp;
  219. _lastClickRect = new Rect(p, new Size())
  220. .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
  221. _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
  222. var e = new PointerPressedEventArgs(source, _pointer, root, p, timestamp, properties, inputModifiers, _clickCount);
  223. source.RaiseEvent(e);
  224. return e.Handled;
  225. }
  226. }
  227. return false;
  228. }
  229. private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties,
  230. KeyModifiers inputModifiers)
  231. {
  232. device = device ?? throw new ArgumentNullException(nameof(device));
  233. root = root ?? throw new ArgumentNullException(nameof(root));
  234. IInputElement? source;
  235. if (_pointer.Captured == null)
  236. {
  237. source = SetPointerOver(this, timestamp, root, p, properties, inputModifiers);
  238. }
  239. else
  240. {
  241. SetPointerOver(this, timestamp, root, _pointer.Captured, properties, inputModifiers);
  242. source = _pointer.Captured;
  243. }
  244. if (source is object)
  245. {
  246. var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root,
  247. p, timestamp, properties, inputModifiers);
  248. source.RaiseEvent(e);
  249. return e.Handled;
  250. }
  251. return false;
  252. }
  253. private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props,
  254. KeyModifiers inputModifiers)
  255. {
  256. device = device ?? throw new ArgumentNullException(nameof(device));
  257. root = root ?? throw new ArgumentNullException(nameof(root));
  258. var hit = HitTest(root, p);
  259. if (hit != null)
  260. {
  261. var source = GetSource(hit);
  262. var e = new PointerReleasedEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers,
  263. _lastMouseDownButton);
  264. source?.RaiseEvent(e);
  265. _pointer.Capture(null);
  266. return e.Handled;
  267. }
  268. return false;
  269. }
  270. private bool MouseWheel(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
  271. PointerPointProperties props,
  272. Vector delta, KeyModifiers inputModifiers)
  273. {
  274. device = device ?? throw new ArgumentNullException(nameof(device));
  275. root = root ?? throw new ArgumentNullException(nameof(root));
  276. var hit = HitTest(root, p);
  277. if (hit != null)
  278. {
  279. var source = GetSource(hit);
  280. var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta);
  281. source?.RaiseEvent(e);
  282. return e.Handled;
  283. }
  284. return false;
  285. }
  286. private IInteractive GetSource(IVisual hit)
  287. {
  288. hit = hit ?? throw new ArgumentNullException(nameof(hit));
  289. return _pointer.Captured ??
  290. (hit as IInteractive) ??
  291. hit.GetSelfAndVisualAncestors().OfType<IInteractive>().FirstOrDefault();
  292. }
  293. private IInputElement? HitTest(IInputElement root, Point p)
  294. {
  295. root = root ?? throw new ArgumentNullException(nameof(root));
  296. return _pointer.Captured ?? root.InputHitTest(p);
  297. }
  298. PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive? source,
  299. PointerPointProperties properties,
  300. KeyModifiers inputModifiers)
  301. {
  302. return new PointerEventArgs(ev, source, _pointer, null, default,
  303. timestamp, properties, inputModifiers);
  304. }
  305. private void ClearPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root,
  306. PointerPointProperties properties,
  307. KeyModifiers inputModifiers)
  308. {
  309. device = device ?? throw new ArgumentNullException(nameof(device));
  310. root = root ?? throw new ArgumentNullException(nameof(root));
  311. var element = root.PointerOverElement;
  312. var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers);
  313. if (element!=null && !element.IsAttachedToVisualTree)
  314. {
  315. // element has been removed from visual tree so do top down cleanup
  316. if (root.IsPointerOver)
  317. ClearChildrenPointerOver(e, root,true);
  318. }
  319. while (element != null)
  320. {
  321. e.Source = element;
  322. e.Handled = false;
  323. element.RaiseEvent(e);
  324. element = (IInputElement?)element.VisualParent;
  325. }
  326. root.PointerOverElement = null;
  327. }
  328. private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot)
  329. {
  330. foreach (IInputElement el in element.VisualChildren)
  331. {
  332. if (el.IsPointerOver)
  333. {
  334. ClearChildrenPointerOver(e, el, true);
  335. break;
  336. }
  337. }
  338. if(clearRoot)
  339. {
  340. e.Source = element;
  341. e.Handled = false;
  342. element.RaiseEvent(e);
  343. }
  344. }
  345. private IInputElement? SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p,
  346. PointerPointProperties properties,
  347. KeyModifiers inputModifiers)
  348. {
  349. device = device ?? throw new ArgumentNullException(nameof(device));
  350. root = root ?? throw new ArgumentNullException(nameof(root));
  351. var element = root.InputHitTest(p);
  352. if (element != root.PointerOverElement)
  353. {
  354. if (element != null)
  355. {
  356. SetPointerOver(device, timestamp, root, element, properties, inputModifiers);
  357. }
  358. else
  359. {
  360. ClearPointerOver(device, timestamp, root, properties, inputModifiers);
  361. }
  362. }
  363. return element;
  364. }
  365. private void SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, IInputElement element,
  366. PointerPointProperties properties,
  367. KeyModifiers inputModifiers)
  368. {
  369. device = device ?? throw new ArgumentNullException(nameof(device));
  370. root = root ?? throw new ArgumentNullException(nameof(root));
  371. element = element ?? throw new ArgumentNullException(nameof(element));
  372. IInputElement? branch = null;
  373. IInputElement? el = element;
  374. while (el != null)
  375. {
  376. if (el.IsPointerOver)
  377. {
  378. branch = el;
  379. break;
  380. }
  381. el = (IInputElement?)el.VisualParent;
  382. }
  383. el = root.PointerOverElement;
  384. var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, el, properties, inputModifiers);
  385. if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
  386. {
  387. ClearChildrenPointerOver(e,branch,false);
  388. }
  389. while (el != null && el != branch)
  390. {
  391. e.Source = el;
  392. e.Handled = false;
  393. el.RaiseEvent(e);
  394. el = (IInputElement?)el.VisualParent;
  395. }
  396. el = root.PointerOverElement = element;
  397. e.RoutedEvent = InputElement.PointerEnterEvent;
  398. while (el != null && el != branch)
  399. {
  400. e.Source = el;
  401. e.Handled = false;
  402. el.RaiseEvent(e);
  403. el = (IInputElement?)el.VisualParent;
  404. }
  405. }
  406. public void Dispose()
  407. {
  408. _disposed = true;
  409. _pointer?.Dispose();
  410. }
  411. }
  412. }