#nullable enable namespace Terminal.Gui; public static partial class Application // Mouse handling { #region Mouse handling /// Disable or enable the mouse. The mouse is enabled by default. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] public static bool IsMouseDisabled { get; set; } /// The current object that wants continuous mouse button pressed events. public static View? WantContinuousButtonPressedView { get; private set; } /// /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to /// this view until the view calls or the mouse is released. /// public static View? MouseGrabView { get; private set; } /// Invoked when a view wants to grab the mouse; can be canceled. public static event EventHandler? GrabbingMouse; /// Invoked when a view wants un-grab the mouse; can be canceled. public static event EventHandler? UnGrabbingMouse; /// Invoked after a view has grabbed the mouse. public static event EventHandler? GrabbedMouse; /// Invoked after a view has un-grabbed the mouse. public static event EventHandler? UnGrabbedMouse; /// /// Grabs the mouse, forcing all mouse events to be routed to the specified view until /// is called. /// /// View that will receive all mouse events until is invoked. public static void GrabMouse (View? view) { if (view is null) { return; } if (!OnGrabbingMouse (view)) { OnGrabbedMouse (view); MouseGrabView = view; } } /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is. public static void UngrabMouse () { if (MouseGrabView is null) { return; } if (!OnUnGrabbingMouse (MouseGrabView)) { View view = MouseGrabView; MouseGrabView = null; OnUnGrabbedMouse (view); } } private static bool OnGrabbingMouse (View? view) { if (view is null) { return false; } var evArgs = new GrabMouseEventArgs (view); GrabbingMouse?.Invoke (view, evArgs); return evArgs.Cancel; } private static bool OnUnGrabbingMouse (View? view) { if (view is null) { return false; } var evArgs = new GrabMouseEventArgs (view); UnGrabbingMouse?.Invoke (view, evArgs); return evArgs.Cancel; } private static void OnGrabbedMouse (View? view) { if (view is null) { return; } GrabbedMouse?.Invoke (view, new (view)); } private static void OnUnGrabbedMouse (View? view) { if (view is null) { return; } UnGrabbedMouse?.Invoke (view, new (view)); } #nullable enable // Used by OnMouseEvent to track the last view that was clicked on. internal static View? MouseEnteredView { get; set; } /// Event fired when a mouse move or click occurs. Coordinates are screen relative. /// /// /// Use this event to receive mouse events in screen coordinates. Use to /// receive mouse events relative to a . /// /// The will contain the that contains the mouse coordinates. /// public static event EventHandler? MouseEvent; /// Called when a mouse event occurs. Raises the event. /// This method can be used to simulate a mouse event, e.g. in unit tests. /// The mouse event with coordinates relative to the screen. internal static void OnMouseEvent (MouseEvent mouseEvent) { if (IsMouseDisabled) { return; } var view = View.FindDeepestView (Current, mouseEvent.Position); if (view is { }) { mouseEvent.View = view; } MouseEvent?.Invoke (null, mouseEvent); if (mouseEvent.Handled) { return; } if (MouseGrabView is { }) { // 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.Position); var viewRelativeMouseEvent = new MouseEvent { Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, View = MouseGrabView }; if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false) { // The mouse has moved outside the bounds of the view that grabbed the mouse MouseEnteredView?.NewMouseLeaveEvent (mouseEvent); } //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) { return; } } if (view is { WantContinuousButtonPressed: true }) { WantContinuousButtonPressedView = view; } else { WantContinuousButtonPressedView = null; } if (view is not Adornment) { if ((view is null || view == ApplicationOverlapped.OverlappedTop) && Current is { Modal: false } && ApplicationOverlapped.OverlappedTop != null && mouseEvent.Flags != MouseFlags.ReportMousePosition && mouseEvent.Flags != 0) { // This occurs when there are multiple overlapped "tops" // E.g. "Mdi" - in the Background Worker Scenario View? top = ApplicationOverlapped.FindDeepestTop (Top!, mouseEvent.Position); view = View.FindDeepestView (top, mouseEvent.Position); if (view is { } && view != ApplicationOverlapped.OverlappedTop && top != Current && top is { }) { ApplicationOverlapped.MoveCurrent ((Toplevel)top); } } } if (view is null) { return; } MouseEvent? me = null; if (view is Adornment adornment) { Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position); me = new () { Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, View = view }; } else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position)) { Point viewportLocation = view.ScreenToViewport (mouseEvent.Position); me = new () { Position = viewportLocation, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, View = view }; } if (me is null) { return; } if (MouseEnteredView is null) { MouseEnteredView = view; view.NewMouseEnterEvent (me); } else if (MouseEnteredView != view) { MouseEnteredView.NewMouseLeaveEvent (me); view.NewMouseEnterEvent (me); MouseEnteredView = view; } if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) { return; } WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}"); while (view.NewMouseEvent (me) != true) { if (MouseGrabView is { }) { break; } if (view is Adornment adornmentView) { view = adornmentView.Parent!.SuperView; } else { view = view.SuperView; } if (view is null) { break; } Point boundsPoint = view.ScreenToViewport (mouseEvent.Position); me = new () { Position = boundsPoint, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, View = view }; } ApplicationOverlapped.BringOverlappedTopToFront (); } #endregion Mouse handling }