Skip to content

Commit

Permalink
Merge pull request gui-cs#3797 from tig/v2_3029-MouseEvents
Browse files Browse the repository at this point in the history
Fixes gui-cs#3029 - Refactors `MouseEvent` and APIs to simplify and make consistent
  • Loading branch information
tig authored Oct 15, 2024
2 parents 23eb14c + e85f694 commit 0a108fa
Show file tree
Hide file tree
Showing 83 changed files with 627 additions and 593 deletions.
2 changes: 1 addition & 1 deletion Terminal.Gui/Application/Application.Initialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ internal static void InternalInit (
private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
private static void Driver_MouseEvent (object? sender, MouseEvent e) { OnMouseEvent (e); }
private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }

/// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
/// <returns></returns>
Expand Down
69 changes: 40 additions & 29 deletions Terminal.Gui/Application/Application.Mouse.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#nullable enable
using System.ComponentModel;
using System.Diagnostics;

namespace Terminal.Gui;

Expand Down Expand Up @@ -45,12 +44,12 @@ public static partial class Application // Mouse handling
/// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
public static void GrabMouse (View? view)
{
if (view is null || OnGrabbingMouse (view))
if (view is null || RaiseGrabbingMouseEvent (view))
{
return;
}

OnGrabbedMouse (view);
RaiseGrabbedMouseEvent (view);
MouseGrabView = view;
}

Expand All @@ -66,16 +65,16 @@ public static void UngrabMouse ()
ObjectDisposedException.ThrowIf (MouseGrabView.WasDisposed, MouseGrabView);
#endif

if (!OnUnGrabbingMouse (MouseGrabView))
if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
{
View view = MouseGrabView;
MouseGrabView = null;
OnUnGrabbedMouse (view);
RaiseUnGrabbedMouseEvent (view);
}
}

/// <exception cref="Exception">A delegate callback throws an exception.</exception>
private static bool OnGrabbingMouse (View? view)
private static bool RaiseGrabbingMouseEvent (View? view)
{
if (view is null)
{
Expand All @@ -89,7 +88,7 @@ private static bool OnGrabbingMouse (View? view)
}

/// <exception cref="Exception">A delegate callback throws an exception.</exception>
private static bool OnUnGrabbingMouse (View? view)
private static bool RaiseUnGrabbingMouseEvent (View? view)
{
if (view is null)
{
Expand All @@ -103,7 +102,7 @@ private static bool OnUnGrabbingMouse (View? view)
}

/// <exception cref="Exception">A delegate callback throws an exception.</exception>
private static void OnGrabbedMouse (View? view)
private static void RaiseGrabbedMouseEvent (View? view)
{
if (view is null)
{
Expand All @@ -114,7 +113,7 @@ private static void OnGrabbedMouse (View? view)
}

/// <exception cref="Exception">A delegate callback throws an exception.</exception>
private static void OnUnGrabbedMouse (View? view)
private static void RaiseUnGrabbedMouseEvent (View? view)
{
if (view is null)
{
Expand All @@ -124,20 +123,14 @@ private static void OnUnGrabbedMouse (View? view)
UnGrabbedMouse?.Invoke (view, new (view));
}

/// <summary>Event fired when a mouse move or click occurs. Coordinates are screen relative.</summary>
/// <remarks>
/// <para>
/// Use this event to receive mouse events in screen coordinates. Use <see cref="MouseEvent"/> to
/// receive mouse events relative to a <see cref="View.Viewport"/>.
/// </para>
/// <para>The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.</para>
/// </remarks>
public static event EventHandler<MouseEvent>? MouseEvent;

/// <summary>Called when a mouse event is raised by the driver.</summary>
/// <summary>
/// INTERNAL API: Called when a mouse event is raised by the driver. Determines the view under the mouse and
/// calls the appropriate View mouse event handlers.
/// </summary>
/// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
/// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
internal static void OnMouseEvent (MouseEvent mouseEvent)
internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
{
_lastMousePosition = mouseEvent.ScreenPosition;

Expand Down Expand Up @@ -177,9 +170,6 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
return;
}

// We can combine this into the switch expression to reduce cognitive complexity even more and likely
// avoid one or two of these checks in the process, as well.

WantContinuousButtonPressedView = deepestViewUnderMouse switch
{
{ WantContinuousButtonPressed: true } => deepestViewUnderMouse,
Expand All @@ -194,7 +184,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
}

// Create a view-relative mouse event to send to the view that is under the mouse.
MouseEvent? viewMouseEvent;
MouseEventArgs? viewMouseEvent;

if (deepestViewUnderMouse is Adornment adornment)
{
Expand All @@ -208,7 +198,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
View = deepestViewUnderMouse
};
}
else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.Position))
else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.ScreenPosition))
{
Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);

Expand All @@ -224,7 +214,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
{
// The mouse was outside any View's Viewport.

// Debug.Fail ("This should never happen. If it does please file an Issue!!");
// Debug.Fail ("This should never happen. If it does please file an Issue!!");

return;
}
Expand Down Expand Up @@ -261,7 +251,29 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
}
}

internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mouseEvent)

#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
/// <summary>
/// Raised when a mouse event occurs. Can be cancelled by setting <see cref="MouseEventArgs.Handled"/> to <see langword="true"/>.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="MouseEventArgs.ScreenPosition"/> coordinates are screen-relative.
/// </para>
/// <para>
/// <see cref="MouseEventArgs.View"/> will be the deepest view under the under the mouse.
/// </para>
/// <para>
/// <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if <see cref="MouseEventArgs.View"/> is set.
/// </para>
/// <para>
/// Use this evento to handle mouse events at the application level, before View-specific handling.
/// </para>
/// </remarks>
public static event EventHandler<MouseEventArgs>? MouseEvent;
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved

internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
{
if (MouseGrabView is { })
{
Expand All @@ -276,7 +288,7 @@ internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mo
// The coordinates are relative to the Bounds of the view that grabbed the mouse.
Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);

var viewRelativeMouseEvent = new MouseEvent
var viewRelativeMouseEvent = new MouseEventArgs
{
Position = frameLoc,
Flags = mouseEvent.Flags,
Expand All @@ -303,7 +315,6 @@ internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mo

internal static readonly List<View?> _cachedViewsUnderMouse = new ();

// TODO: Refactor MouseEnter/LeaveEvents to not take MouseEvent param.
/// <summary>
/// INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -588,11 +588,11 @@ public virtual Attribute MakeColor (in Color foreground, in Color background)
public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }

/// <summary>Event fired when a mouse event occurs.</summary>
public event EventHandler<MouseEvent>? MouseEvent;
public event EventHandler<MouseEventArgs>? MouseEvent;

/// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
/// <param name="a"></param>
public void OnMouseEvent (MouseEvent a)
public void OnMouseEvent (MouseEventArgs a)
{
// Ensure ScreenPosition is set
a.ScreenPosition = a.Position;
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ bool IsButtonClickedOrDoubleClicked (MouseFlags flag)

_lastMouseFlags = mouseFlag;

var me = new MouseEvent { Flags = mouseFlag, Position = pos };
var me = new MouseEventArgs { Flags = mouseFlag, Position = pos };
//Debug.WriteLine ($"CursesDriver: ({me.Position}) - {me.Flags}");

OnMouseEvent (me);
Expand Down
6 changes: 3 additions & 3 deletions Terminal.Gui/ConsoleDrivers/NetDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ private void ProcessInput (InputResult inputEvent)

break;
case EventType.Mouse:
MouseEvent me = ToDriverMouse (inputEvent.MouseEvent);
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
//Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
OnMouseEvent (me);

Expand Down Expand Up @@ -1393,7 +1393,7 @@ public void StopReportingMouseMoves ()
}
}

private MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me)
{
//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");

Expand Down Expand Up @@ -1539,7 +1539,7 @@ private MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
mouseFlag |= MouseFlags.ButtonAlt;
}

return new MouseEvent { Position = me.Position, Flags = mouseFlag };
return new MouseEventArgs { Position = me.Position, Flags = mouseFlag };
}

#endregion Mouse Handling
Expand Down
10 changes: 5 additions & 5 deletions Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1483,7 +1483,7 @@ internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
break;

case WindowsConsole.EventType.Mouse:
MouseEvent me = ToDriverMouse (inputEvent.MouseEvent);
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);

if (me is null || me.Flags == MouseFlags.None)
{
Expand Down Expand Up @@ -1827,9 +1827,9 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
}
await Task.Delay (delay);

var me = new MouseEvent
var me = new MouseEventArgs
{
Position = _pointMove,
ScreenPosition = _pointMove,
Flags = mouseFlag
};

Expand Down Expand Up @@ -1883,7 +1883,7 @@ private static MouseFlags SetControlKeyStates (WindowsConsole.MouseEventRecord m
}

[CanBeNull]
private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
{
var mouseFlag = MouseFlags.AllEvents;

Expand Down Expand Up @@ -2127,7 +2127,7 @@ private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
//System.Diagnostics.Debug.WriteLine (
// $"point.X:{(point is { } ? ((Point)point).X : -1)};point.Y:{(point is { } ? ((Point)point).Y : -1)}");

return new MouseEvent
return new MouseEventArgs
{
Position = new (mouseEvent.MousePosition.X, mouseEvent.MousePosition.Y),
Flags = mouseFlag
Expand Down
101 changes: 101 additions & 0 deletions Terminal.Gui/Input/MouseEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#nullable enable
using System.ComponentModel;

namespace Terminal.Gui;

/// <summary>
/// Specifies the event arguments for <see cref="Terminal.Gui.MouseEventArgs"/>. This is a higher-level construct than
/// the wrapped <see cref="Terminal.Gui.MouseEventArgs"/> class and is used for the events defined on
/// <see cref="View"/> and subclasses
/// of View (e.g. <see cref="View.MouseEnter"/> and <see cref="View.MouseClick"/>).
/// </summary>
public class MouseEventArgs : HandledEventArgs
{
/// <summary>
/// Flags indicating the state of the mouse buttons and the type of event that occurred.
/// </summary>
public MouseFlags Flags { get; set; }

/// <summary>
/// The screen-relative mouse position.
/// </summary>
public Point ScreenPosition { get; set; }

/// <summary>The deepest View who's <see cref="View.Frame"/> contains <see cref="ScreenPosition"/>.</summary>
public View? View { get; set; }

/// <summary>
/// The position of the mouse in <see cref="View"/>'s Viewport-relative coordinates. Only valid if <see cref="View"/>
/// is set.
/// </summary>
public Point Position { get; set; }

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the button pressed related flags.
/// </summary>
public bool IsPressed => Flags.HasFlag (MouseFlags.Button1Pressed)
|| Flags.HasFlag (MouseFlags.Button2Pressed)
|| Flags.HasFlag (MouseFlags.Button3Pressed)
|| Flags.HasFlag (MouseFlags.Button4Pressed);

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the button released related flags.
/// </summary>
public bool IsReleased => Flags.HasFlag (MouseFlags.Button1Released)
|| Flags.HasFlag (MouseFlags.Button2Released)
|| Flags.HasFlag (MouseFlags.Button3Released)
|| Flags.HasFlag (MouseFlags.Button4Released);

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the single-clicked related flags.
/// </summary>
public bool IsSingleClicked => Flags.HasFlag (MouseFlags.Button1Clicked)
|| Flags.HasFlag (MouseFlags.Button2Clicked)
|| Flags.HasFlag (MouseFlags.Button3Clicked)
|| Flags.HasFlag (MouseFlags.Button4Clicked);

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the double-clicked related flags.
/// </summary>
public bool IsDoubleClicked => Flags.HasFlag (MouseFlags.Button1DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button2DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button3DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button4DoubleClicked);

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the triple-clicked related flags.
/// </summary>
public bool IsTripleClicked => Flags.HasFlag (MouseFlags.Button1TripleClicked)
|| Flags.HasFlag (MouseFlags.Button2TripleClicked)
|| Flags.HasFlag (MouseFlags.Button3TripleClicked)
|| Flags.HasFlag (MouseFlags.Button4TripleClicked);

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the mouse button clicked related flags.
/// </summary>
public bool IsSingleDoubleOrTripleClicked =>
Flags.HasFlag (MouseFlags.Button1Clicked)
|| Flags.HasFlag (MouseFlags.Button2Clicked)
|| Flags.HasFlag (MouseFlags.Button3Clicked)
|| Flags.HasFlag (MouseFlags.Button4Clicked)
|| Flags.HasFlag (MouseFlags.Button1DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button2DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button3DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button4DoubleClicked)
|| Flags.HasFlag (MouseFlags.Button1TripleClicked)
|| Flags.HasFlag (MouseFlags.Button2TripleClicked)
|| Flags.HasFlag (MouseFlags.Button3TripleClicked)
|| Flags.HasFlag (MouseFlags.Button4TripleClicked);

/// <summary>
/// Gets whether <see cref="Flags"/> contains any of the mouse wheel related flags.
/// </summary>
public bool IsWheel => Flags.HasFlag (MouseFlags.WheeledDown)
|| Flags.HasFlag (MouseFlags.WheeledUp)
|| Flags.HasFlag (MouseFlags.WheeledLeft)
|| Flags.HasFlag (MouseFlags.WheeledRight);

/// <summary>Returns a <see cref="T:System.String"/> that represents the current <see cref="Terminal.Gui.MouseEventArgs"/>.</summary>
/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="Terminal.Gui.MouseEventArgs"/>.</returns>
public override string ToString () { return $"({ScreenPosition}):{Flags}:{View?.Id}:{Position}"; }
}
Loading

0 comments on commit 0a108fa

Please sign in to comment.