MouseGrabHandler.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #nullable enable
  2. namespace Terminal.Gui.App;
  3. /// <summary>
  4. /// INTERNAL: Implements <see cref="IMouseGrabHandler"/> to manage which <see cref="View"/> (if any) has 'grabbed' the mouse,
  5. /// giving it exclusive priority for mouse events such as movement, button presses, and release.
  6. /// <para>
  7. /// Used for scenarios like dragging, scrolling, or any interaction where a view needs to receive all mouse events
  8. /// until the operation completes (e.g., a scrollbar thumb being dragged).
  9. /// </para>
  10. /// <para>
  11. /// See <see cref="IMouseGrabHandler"/> for usage details.
  12. /// </para>
  13. /// </summary>
  14. internal class MouseGrabHandler : IMouseGrabHandler
  15. {
  16. /// <inheritdoc/>
  17. public View? MouseGrabView { get; private set; }
  18. /// <inheritdoc/>
  19. public event EventHandler<GrabMouseEventArgs>? GrabbingMouse;
  20. /// <inheritdoc/>
  21. public event EventHandler<GrabMouseEventArgs>? UnGrabbingMouse;
  22. /// <inheritdoc/>
  23. public event EventHandler<ViewEventArgs>? GrabbedMouse;
  24. /// <inheritdoc/>
  25. public event EventHandler<ViewEventArgs>? UnGrabbedMouse;
  26. /// <inheritdoc/>
  27. public void GrabMouse (View? view)
  28. {
  29. if (view is null || RaiseGrabbingMouseEvent (view))
  30. {
  31. return;
  32. }
  33. RaiseGrabbedMouseEvent (view);
  34. // MouseGrabView is a static; only set if the application is initialized.
  35. MouseGrabView = view;
  36. }
  37. /// <inheritdoc/>
  38. public void UngrabMouse ()
  39. {
  40. if (MouseGrabView is null)
  41. {
  42. return;
  43. }
  44. #if DEBUG_IDISPOSABLE
  45. if (View.EnableDebugIDisposableAsserts)
  46. {
  47. ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
  48. }
  49. #endif
  50. if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
  51. {
  52. View view = MouseGrabView;
  53. MouseGrabView = null;
  54. RaiseUnGrabbedMouseEvent (view);
  55. }
  56. }
  57. /// <exception cref="Exception">A delegate callback throws an exception.</exception>
  58. private bool RaiseGrabbingMouseEvent (View? view)
  59. {
  60. if (view is null)
  61. {
  62. return false;
  63. }
  64. var evArgs = new GrabMouseEventArgs (view);
  65. GrabbingMouse?.Invoke (view, evArgs);
  66. return evArgs.Cancel;
  67. }
  68. /// <exception cref="Exception">A delegate callback throws an exception.</exception>
  69. private bool RaiseUnGrabbingMouseEvent (View? view)
  70. {
  71. if (view is null)
  72. {
  73. return false;
  74. }
  75. var evArgs = new GrabMouseEventArgs (view);
  76. UnGrabbingMouse?.Invoke (view, evArgs);
  77. return evArgs.Cancel;
  78. }
  79. /// <exception cref="Exception">A delegate callback throws an exception.</exception>
  80. private void RaiseGrabbedMouseEvent (View? view)
  81. {
  82. if (view is null)
  83. {
  84. return;
  85. }
  86. GrabbedMouse?.Invoke (view, new (view));
  87. }
  88. /// <exception cref="Exception">A delegate callback throws an exception.</exception>
  89. private void RaiseUnGrabbedMouseEvent (View? view)
  90. {
  91. if (view is null)
  92. {
  93. return;
  94. }
  95. UnGrabbedMouse?.Invoke (view, new (view));
  96. }
  97. /// <inheritdoc/>
  98. public bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
  99. {
  100. if (MouseGrabView is { })
  101. {
  102. #if DEBUG_IDISPOSABLE
  103. if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed)
  104. {
  105. throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
  106. }
  107. #endif
  108. // If the mouse is grabbed, send the event to the view that grabbed it.
  109. // The coordinates are relative to the Bounds of the view that grabbed the mouse.
  110. Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
  111. var viewRelativeMouseEvent = new MouseEventArgs
  112. {
  113. Position = frameLoc,
  114. Flags = mouseEvent.Flags,
  115. ScreenPosition = mouseEvent.ScreenPosition,
  116. View = deepestViewUnderMouse ?? MouseGrabView
  117. };
  118. //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
  119. if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
  120. {
  121. return true;
  122. }
  123. // ReSharper disable once ConditionIsAlwaysTrueOrFalse
  124. if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
  125. {
  126. // The view that grabbed the mouse has been disposed
  127. return true;
  128. }
  129. }
  130. return false;
  131. }
  132. }