Skip to content

Commit 0a108fa

Browse files
authored
Merge pull request #3797 from tig/v2_3029-MouseEvents
Fixes #3029 - Refactors `MouseEvent` and APIs to simplify and make consistent
2 parents 23eb14c + e85f694 commit 0a108fa

File tree

83 files changed

+627
-593
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+627
-593
lines changed

Terminal.Gui/Application/Application.Initialization.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ internal static void InternalInit (
180180
private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
181181
private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
182182
private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
183-
private static void Driver_MouseEvent (object? sender, MouseEvent e) { OnMouseEvent (e); }
183+
private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
184184

185185
/// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
186186
/// <returns></returns>

Terminal.Gui/Application/Application.Mouse.cs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#nullable enable
22
using System.ComponentModel;
3-
using System.Diagnostics;
43

54
namespace Terminal.Gui;
65

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

53-
OnGrabbedMouse (view);
52+
RaiseGrabbedMouseEvent (view);
5453
MouseGrabView = view;
5554
}
5655

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

69-
if (!OnUnGrabbingMouse (MouseGrabView))
68+
if (!RaiseUnGrabbingMouseEvent (MouseGrabView))
7069
{
7170
View view = MouseGrabView;
7271
MouseGrabView = null;
73-
OnUnGrabbedMouse (view);
72+
RaiseUnGrabbedMouseEvent (view);
7473
}
7574
}
7675

7776
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
78-
private static bool OnGrabbingMouse (View? view)
77+
private static bool RaiseGrabbingMouseEvent (View? view)
7978
{
8079
if (view is null)
8180
{
@@ -89,7 +88,7 @@ private static bool OnGrabbingMouse (View? view)
8988
}
9089

9190
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
92-
private static bool OnUnGrabbingMouse (View? view)
91+
private static bool RaiseUnGrabbingMouseEvent (View? view)
9392
{
9493
if (view is null)
9594
{
@@ -103,7 +102,7 @@ private static bool OnUnGrabbingMouse (View? view)
103102
}
104103

105104
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
106-
private static void OnGrabbedMouse (View? view)
105+
private static void RaiseGrabbedMouseEvent (View? view)
107106
{
108107
if (view is null)
109108
{
@@ -114,7 +113,7 @@ private static void OnGrabbedMouse (View? view)
114113
}
115114

116115
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
117-
private static void OnUnGrabbedMouse (View? view)
116+
private static void RaiseUnGrabbedMouseEvent (View? view)
118117
{
119118
if (view is null)
120119
{
@@ -124,20 +123,14 @@ private static void OnUnGrabbedMouse (View? view)
124123
UnGrabbedMouse?.Invoke (view, new (view));
125124
}
126125

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

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

@@ -177,9 +170,6 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
177170
return;
178171
}
179172

180-
// We can combine this into the switch expression to reduce cognitive complexity even more and likely
181-
// avoid one or two of these checks in the process, as well.
182-
183173
WantContinuousButtonPressedView = deepestViewUnderMouse switch
184174
{
185175
{ WantContinuousButtonPressed: true } => deepestViewUnderMouse,
@@ -194,7 +184,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
194184
}
195185

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

199189
if (deepestViewUnderMouse is Adornment adornment)
200190
{
@@ -208,7 +198,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
208198
View = deepestViewUnderMouse
209199
};
210200
}
211-
else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.Position))
201+
else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.ScreenPosition))
212202
{
213203
Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
214204

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

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

229219
return;
230220
}
@@ -261,7 +251,29 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
261251
}
262252
}
263253

264-
internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mouseEvent)
254+
255+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
256+
/// <summary>
257+
/// Raised when a mouse event occurs. Can be cancelled by setting <see cref="MouseEventArgs.Handled"/> to <see langword="true"/>.
258+
/// </summary>
259+
/// <remarks>
260+
/// <para>
261+
/// <see cref="MouseEventArgs.ScreenPosition"/> coordinates are screen-relative.
262+
/// </para>
263+
/// <para>
264+
/// <see cref="MouseEventArgs.View"/> will be the deepest view under the under the mouse.
265+
/// </para>
266+
/// <para>
267+
/// <see cref="MouseEventArgs.Position"/> coordinates are view-relative. Only valid if <see cref="MouseEventArgs.View"/> is set.
268+
/// </para>
269+
/// <para>
270+
/// Use this evento to handle mouse events at the application level, before View-specific handling.
271+
/// </para>
272+
/// </remarks>
273+
public static event EventHandler<MouseEventArgs>? MouseEvent;
274+
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
275+
276+
internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEventArgs mouseEvent)
265277
{
266278
if (MouseGrabView is { })
267279
{
@@ -276,7 +288,7 @@ internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mo
276288
// The coordinates are relative to the Bounds of the view that grabbed the mouse.
277289
Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
278290

279-
var viewRelativeMouseEvent = new MouseEvent
291+
var viewRelativeMouseEvent = new MouseEventArgs
280292
{
281293
Position = frameLoc,
282294
Flags = mouseEvent.Flags,
@@ -303,7 +315,6 @@ internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mo
303315

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

306-
// TODO: Refactor MouseEnter/LeaveEvents to not take MouseEvent param.
307318
/// <summary>
308319
/// INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
309320
/// </summary>

Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,11 +588,11 @@ public virtual Attribute MakeColor (in Color foreground, in Color background)
588588
public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
589589

590590
/// <summary>Event fired when a mouse event occurs.</summary>
591-
public event EventHandler<MouseEvent>? MouseEvent;
591+
public event EventHandler<MouseEventArgs>? MouseEvent;
592592

593593
/// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
594594
/// <param name="a"></param>
595-
public void OnMouseEvent (MouseEvent a)
595+
public void OnMouseEvent (MouseEventArgs a)
596596
{
597597
// Ensure ScreenPosition is set
598598
a.ScreenPosition = a.Position;

Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
10041004

10051005
_lastMouseFlags = mouseFlag;
10061006

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

10101010
OnMouseEvent (me);

Terminal.Gui/ConsoleDrivers/NetDriver.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ private void ProcessInput (InputResult inputEvent)
11541154

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

@@ -1393,7 +1393,7 @@ public void StopReportingMouseMoves ()
13931393
}
13941394
}
13951395

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

@@ -1539,7 +1539,7 @@ private MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
15391539
mouseFlag |= MouseFlags.ButtonAlt;
15401540
}
15411541

1542-
return new MouseEvent { Position = me.Position, Flags = mouseFlag };
1542+
return new MouseEventArgs { Position = me.Position, Flags = mouseFlag };
15431543
}
15441544

15451545
#endregion Mouse Handling

Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,7 @@ internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
14831483
break;
14841484

14851485
case WindowsConsole.EventType.Mouse:
1486-
MouseEvent me = ToDriverMouse (inputEvent.MouseEvent);
1486+
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
14871487

14881488
if (me is null || me.Flags == MouseFlags.None)
14891489
{
@@ -1827,9 +1827,9 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
18271827
}
18281828
await Task.Delay (delay);
18291829

1830-
var me = new MouseEvent
1830+
var me = new MouseEventArgs
18311831
{
1832-
Position = _pointMove,
1832+
ScreenPosition = _pointMove,
18331833
Flags = mouseFlag
18341834
};
18351835

@@ -1883,7 +1883,7 @@ private static MouseFlags SetControlKeyStates (WindowsConsole.MouseEventRecord m
18831883
}
18841884

18851885
[CanBeNull]
1886-
private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
1886+
private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
18871887
{
18881888
var mouseFlag = MouseFlags.AllEvents;
18891889

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

2130-
return new MouseEvent
2130+
return new MouseEventArgs
21312131
{
21322132
Position = new (mouseEvent.MousePosition.X, mouseEvent.MousePosition.Y),
21332133
Flags = mouseFlag

Terminal.Gui/Input/MouseEventArgs.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#nullable enable
2+
using System.ComponentModel;
3+
4+
namespace Terminal.Gui;
5+
6+
/// <summary>
7+
/// Specifies the event arguments for <see cref="Terminal.Gui.MouseEventArgs"/>. This is a higher-level construct than
8+
/// the wrapped <see cref="Terminal.Gui.MouseEventArgs"/> class and is used for the events defined on
9+
/// <see cref="View"/> and subclasses
10+
/// of View (e.g. <see cref="View.MouseEnter"/> and <see cref="View.MouseClick"/>).
11+
/// </summary>
12+
public class MouseEventArgs : HandledEventArgs
13+
{
14+
/// <summary>
15+
/// Flags indicating the state of the mouse buttons and the type of event that occurred.
16+
/// </summary>
17+
public MouseFlags Flags { get; set; }
18+
19+
/// <summary>
20+
/// The screen-relative mouse position.
21+
/// </summary>
22+
public Point ScreenPosition { get; set; }
23+
24+
/// <summary>The deepest View who's <see cref="View.Frame"/> contains <see cref="ScreenPosition"/>.</summary>
25+
public View? View { get; set; }
26+
27+
/// <summary>
28+
/// The position of the mouse in <see cref="View"/>'s Viewport-relative coordinates. Only valid if <see cref="View"/>
29+
/// is set.
30+
/// </summary>
31+
public Point Position { get; set; }
32+
33+
/// <summary>
34+
/// Gets whether <see cref="Flags"/> contains any of the button pressed related flags.
35+
/// </summary>
36+
public bool IsPressed => Flags.HasFlag (MouseFlags.Button1Pressed)
37+
|| Flags.HasFlag (MouseFlags.Button2Pressed)
38+
|| Flags.HasFlag (MouseFlags.Button3Pressed)
39+
|| Flags.HasFlag (MouseFlags.Button4Pressed);
40+
41+
/// <summary>
42+
/// Gets whether <see cref="Flags"/> contains any of the button released related flags.
43+
/// </summary>
44+
public bool IsReleased => Flags.HasFlag (MouseFlags.Button1Released)
45+
|| Flags.HasFlag (MouseFlags.Button2Released)
46+
|| Flags.HasFlag (MouseFlags.Button3Released)
47+
|| Flags.HasFlag (MouseFlags.Button4Released);
48+
49+
/// <summary>
50+
/// Gets whether <see cref="Flags"/> contains any of the single-clicked related flags.
51+
/// </summary>
52+
public bool IsSingleClicked => Flags.HasFlag (MouseFlags.Button1Clicked)
53+
|| Flags.HasFlag (MouseFlags.Button2Clicked)
54+
|| Flags.HasFlag (MouseFlags.Button3Clicked)
55+
|| Flags.HasFlag (MouseFlags.Button4Clicked);
56+
57+
/// <summary>
58+
/// Gets whether <see cref="Flags"/> contains any of the double-clicked related flags.
59+
/// </summary>
60+
public bool IsDoubleClicked => Flags.HasFlag (MouseFlags.Button1DoubleClicked)
61+
|| Flags.HasFlag (MouseFlags.Button2DoubleClicked)
62+
|| Flags.HasFlag (MouseFlags.Button3DoubleClicked)
63+
|| Flags.HasFlag (MouseFlags.Button4DoubleClicked);
64+
65+
/// <summary>
66+
/// Gets whether <see cref="Flags"/> contains any of the triple-clicked related flags.
67+
/// </summary>
68+
public bool IsTripleClicked => Flags.HasFlag (MouseFlags.Button1TripleClicked)
69+
|| Flags.HasFlag (MouseFlags.Button2TripleClicked)
70+
|| Flags.HasFlag (MouseFlags.Button3TripleClicked)
71+
|| Flags.HasFlag (MouseFlags.Button4TripleClicked);
72+
73+
/// <summary>
74+
/// Gets whether <see cref="Flags"/> contains any of the mouse button clicked related flags.
75+
/// </summary>
76+
public bool IsSingleDoubleOrTripleClicked =>
77+
Flags.HasFlag (MouseFlags.Button1Clicked)
78+
|| Flags.HasFlag (MouseFlags.Button2Clicked)
79+
|| Flags.HasFlag (MouseFlags.Button3Clicked)
80+
|| Flags.HasFlag (MouseFlags.Button4Clicked)
81+
|| Flags.HasFlag (MouseFlags.Button1DoubleClicked)
82+
|| Flags.HasFlag (MouseFlags.Button2DoubleClicked)
83+
|| Flags.HasFlag (MouseFlags.Button3DoubleClicked)
84+
|| Flags.HasFlag (MouseFlags.Button4DoubleClicked)
85+
|| Flags.HasFlag (MouseFlags.Button1TripleClicked)
86+
|| Flags.HasFlag (MouseFlags.Button2TripleClicked)
87+
|| Flags.HasFlag (MouseFlags.Button3TripleClicked)
88+
|| Flags.HasFlag (MouseFlags.Button4TripleClicked);
89+
90+
/// <summary>
91+
/// Gets whether <see cref="Flags"/> contains any of the mouse wheel related flags.
92+
/// </summary>
93+
public bool IsWheel => Flags.HasFlag (MouseFlags.WheeledDown)
94+
|| Flags.HasFlag (MouseFlags.WheeledUp)
95+
|| Flags.HasFlag (MouseFlags.WheeledLeft)
96+
|| Flags.HasFlag (MouseFlags.WheeledRight);
97+
98+
/// <summary>Returns a <see cref="T:System.String"/> that represents the current <see cref="Terminal.Gui.MouseEventArgs"/>.</summary>
99+
/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="Terminal.Gui.MouseEventArgs"/>.</returns>
100+
public override string ToString () { return $"({ScreenPosition}):{Flags}:{View?.Id}:{Position}"; }
101+
}

0 commit comments

Comments
 (0)