Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3029 - Refactors MouseEvent and APIs to simplify and make consistent #3797

Merged
merged 21 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading