MouseGrabHandler.cs 4.6 KB

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