namespace Terminal.Gui.App;
///
/// INTERNAL: Implements to manage which (if any) has 'grabbed' the mouse,
/// giving it exclusive priority for mouse events such as movement, button presses, and release.
///
/// Used for scenarios like dragging, scrolling, or any interaction where a view needs to receive all mouse events
/// until the operation completes (e.g., a scrollbar thumb being dragged).
///
///
/// See for usage details.
///
///
internal class MouseGrabHandler : IMouseGrabHandler
{
///
public View? MouseGrabView { get; private set; }
///
public event EventHandler? GrabbingMouse;
///
public event EventHandler? UnGrabbingMouse;
///
public event EventHandler? GrabbedMouse;
///
public event EventHandler? UnGrabbedMouse;
///
public void GrabMouse (View? view)
{
if (view is null || RaiseGrabbingMouseEvent (view))
{
return;
}
RaiseGrabbedMouseEvent (view);
// MouseGrabView is a static; only set if the application is initialized.
MouseGrabView = view;
}
///
public void UngrabMouse ()
{
if (MouseGrabView is null)
{
return;
}
#if DEBUG_IDISPOSABLE
if (View.EnableDebugIDisposableAsserts)
{
ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
}
#endif
if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
{
View view = MouseGrabView;
MouseGrabView = null;
RaiseUnGrabbedMouseEvent (view);
}
}
/// A delegate callback throws an exception.
private bool RaiseGrabbingMouseEvent (View? view)
{
if (view is null)
{
return false;
}
var evArgs = new GrabMouseEventArgs (view);
GrabbingMouse?.Invoke (view, evArgs);
return evArgs.Cancel;
}
/// A delegate callback throws an exception.
private bool RaiseUnGrabbingMouseEvent (View? view)
{
if (view is null)
{
return false;
}
var evArgs = new GrabMouseEventArgs (view);
UnGrabbingMouse?.Invoke (view, evArgs);
return evArgs.Cancel;
}
/// A delegate callback throws an exception.
private void RaiseGrabbedMouseEvent (View? view)
{
if (view is null)
{
return;
}
GrabbedMouse?.Invoke (view, new (view));
}
/// A delegate callback throws an exception.
private void RaiseUnGrabbedMouseEvent (View? view)
{
if (view is null)
{
return;
}
UnGrabbedMouse?.Invoke (view, new (view));
}
///
public bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
{
if (MouseGrabView is { })
{
#if DEBUG_IDISPOSABLE
if (View.EnableDebugIDisposableAsserts && MouseGrabView.WasDisposed)
{
throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
}
#endif
// If the mouse is grabbed, send the event to the view that grabbed it.
// The coordinates are relative to the Bounds of the view that grabbed the mouse.
Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
var viewRelativeMouseEvent = new MouseEventArgs
{
Position = frameLoc,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.ScreenPosition,
View = deepestViewUnderMouse ?? MouseGrabView
};
//System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
{
return true;
}
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
{
// The view that grabbed the mouse has been disposed
return true;
}
}
return false;
}
}