From dd76a57628de47e3f600dcc9d19c95ce5e3f058a Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 14:36:41 +0100 Subject: [PATCH 01/11] Implemented mouse button clicked logic. --- Terminal.Gui/Application.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 1901bba2ac..992fd499ff 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -143,6 +143,8 @@ internal static void ResetState () // Mouse _mouseEnteredView = null; + _lastViewButtonPressed = null; + _canProcessClickedEvent = true; WantContinuousButtonPressedView = null; MouseEvent = null; GrabbedMouse = null; @@ -1432,6 +1434,8 @@ private static void OnUnGrabbedMouse (View view) // Used by OnMouseEvent to track the last view that was clicked on. internal static View? _mouseEnteredView; + internal static View? _lastViewButtonPressed; + internal static bool _canProcessClickedEvent = true; /// Event fired when a mouse move or click occurs. Coordinates are screen relative. /// @@ -1461,6 +1465,26 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) mouseEvent.View = view; } + if (_lastViewButtonPressed is null && mouseEvent.Flags is MouseFlags.Button1Pressed or MouseFlags.Button2Pressed or MouseFlags.Button3Pressed or MouseFlags.Button4Pressed) + { + _lastViewButtonPressed = view; + } + else if (_lastViewButtonPressed is { } && _lastViewButtonPressed != view && mouseEvent.Flags is MouseFlags.Button1Released or MouseFlags.Button2Released or MouseFlags.Button3Released or MouseFlags.Button4Released) + { + _lastViewButtonPressed = null; + _canProcessClickedEvent = false; + } + else if (!_canProcessClickedEvent && mouseEvent.Flags is MouseFlags.Button1Clicked or MouseFlags.Button2Clicked or MouseFlags.Button3Clicked or MouseFlags.Button4Clicked) + { + _canProcessClickedEvent = true; + + return; + } + else if (!mouseEvent.Flags.HasFlag(MouseFlags.ReportMousePosition)) + { + _lastViewButtonPressed = null; + } + MouseEvent?.Invoke (null, mouseEvent); if (mouseEvent.Handled) From fe6601abf0c0d95815fcf078ed6ce3f347b19f9e Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 14:41:18 +0100 Subject: [PATCH 02/11] Allowing the mouse enter event always raised when the mouse is hover a view. --- Terminal.Gui/Application.cs | 28 ++++++-- UnitTests/Application/MouseTests.cs | 99 +++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 992fd499ff..fbce3d3b06 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -1504,13 +1504,22 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Y = boundsLoc.Y, Flags = mouseEvent.Flags, ScreenPosition = new (mouseEvent.X, mouseEvent.Y), - View = MouseGrabView + View = view ?? MouseGrabView }; if (MouseGrabView.Bounds.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false) { // The mouse has moved outside the bounds of the view that grabbed the mouse - _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent); + if (_mouseEnteredView?.NewMouseLeaveEvent (mouseEvent) == true) + { + return; + } + + // Give a chance for the current view process the event + if (ProcessMouseEvent (mouseEvent, view)) + { + return; + } } //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); @@ -1520,6 +1529,11 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) } } + ProcessMouseEvent (mouseEvent, view); + } + + private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) + { if (view is { WantContinuousButtonPressed: true }) { WantContinuousButtonPressedView = view; @@ -1552,7 +1566,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (view is null) { - return; + return false; } MouseEvent? me = null; @@ -1586,7 +1600,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (me is null) { - return; + return false; } if (_mouseEnteredView is null) @@ -1603,7 +1617,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) { - return; + return false; } WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; @@ -1613,10 +1627,12 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (view.NewMouseEvent (me) == false) { // Should we bubble up the event, if it is not handled? - //return; + return false; } BringOverlappedTopToFront (); + + return true; } #nullable restore diff --git a/UnitTests/Application/MouseTests.cs b/UnitTests/Application/MouseTests.cs index 559cfbb9ef..eda1904c0b 100644 --- a/UnitTests/Application/MouseTests.cs +++ b/UnitTests/Application/MouseTests.cs @@ -371,5 +371,104 @@ void Application_UnGrabbedMouse (object sender, ViewEventArgs e) } } + [Fact] + [AutoInitShutdown] + public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () + { + var view1Clicked = false; + var view2Clicked = false; + var view1 = new View { Id = "view1", CanFocus = true, Width = 10, Height = 1 }; + view1.MouseClick += (s, e) => view1Clicked = true; + var view2 = new View { Id = "view1", CanFocus = true, Y = 2, Width = 10, Height = 1 }; + view2.MouseClick += (s, e) => view2Clicked = true; + var top = new Toplevel (); + top.Add (view1, view2); + Application.Begin (top); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Assert.Equal (Application._mouseEnteredView, view1); + Assert.Equal (Application._lastViewButtonPressed, view1); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Equal (Application._lastViewButtonPressed, view1); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Released }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Null (Application._lastViewButtonPressed); + Assert.False (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Clicked }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Null (Application._lastViewButtonPressed); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Pressed }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Equal (Application._lastViewButtonPressed, view2); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Assert.Equal (Application._mouseEnteredView, view1); + Assert.Equal (Application._lastViewButtonPressed, view2); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Released }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Null (Application._lastViewButtonPressed); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.False (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Clicked }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Null (Application._lastViewButtonPressed); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.True (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Assert.Equal (Application._mouseEnteredView, view1); + Assert.Equal (Application._lastViewButtonPressed, view1); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.True (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Assert.Equal (Application._mouseEnteredView, view2); + Assert.Equal (Application._lastViewButtonPressed, view1); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.True (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Released }); + Assert.Equal (Application._mouseEnteredView, view1); + Assert.Null (Application._lastViewButtonPressed); + Assert.True (Application._canProcessClickedEvent); + Assert.False (view1Clicked); + Assert.True (view2Clicked); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked }); + Assert.Equal (Application._mouseEnteredView, view1); + Assert.Null (Application._lastViewButtonPressed); + Assert.True (Application._canProcessClickedEvent); + Assert.True (view1Clicked); + Assert.True (view2Clicked); + } + #endregion } From 8d92e147e3d615087c5824d4ea1e47942e5f67a1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 14:43:37 +0100 Subject: [PATCH 03/11] Change hot color on highlight. --- Terminal.Gui/View/ViewMouse.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index fa94be54f7..1a3bcb844a 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -477,6 +477,7 @@ internal bool SetHighlight (HighlightStyle style) { // Highlight the foreground focus color Focus = new (ColorScheme.Focus.Foreground.GetHighlightColor (), ColorScheme.Focus.Background.GetHighlightColor ()), + HotFocus = new (ColorScheme.HotFocus.Foreground.GetHighlightColor (), ColorScheme.HotFocus.Background.GetHighlightColor ()) }; ColorScheme = cs; } @@ -485,7 +486,8 @@ internal bool SetHighlight (HighlightStyle style) var cs = new ColorScheme (ColorScheme) { // Invert Focus color foreground/background. We can do this because we know the view is not going to be focused. - Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground) + Normal = new (ColorScheme.Focus.Background, ColorScheme.Normal.Foreground), + HotNormal = new (ColorScheme.HotFocus.Background, ColorScheme.HotNormal.Foreground) }; ColorScheme = cs; } From 762a33743beb2583c9a52048ea5bd45cb07b11c1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 14:44:17 +0100 Subject: [PATCH 04/11] Formatting. --- Terminal.Gui/View/ViewMouse.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index 1a3bcb844a..0938516b92 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -166,7 +166,7 @@ public partial class View return false; } - if (OnMouseLeave (mouseEvent) == true) + if (OnMouseLeave (mouseEvent)) { return true; } @@ -329,15 +329,14 @@ private bool HandlePressed (MouseEvent mouseEvent) if (Bounds.Contains (mouseEvent.X, mouseEvent.Y)) { - if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None) == true) + if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None)) { return true; } } else { - if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None) == true) - + if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None)) { return true; } From ae3c4b8c229f55e19374b633206685650a919130 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 14:53:41 +0100 Subject: [PATCH 05/11] Remove duplicate code which is already ran in the ProcessMouseEvent method. --- Terminal.Gui/Application.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index fbce3d3b06..ba3d3f15fc 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -1510,11 +1510,6 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (MouseGrabView.Bounds.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false) { // The mouse has moved outside the bounds of the view that grabbed the mouse - if (_mouseEnteredView?.NewMouseLeaveEvent (mouseEvent) == true) - { - return; - } - // Give a chance for the current view process the event if (ProcessMouseEvent (mouseEvent, view)) { From a81084f70256069aca1cf820fffb51461b3ae262 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 16:51:28 +0100 Subject: [PATCH 06/11] Add bool? IsMouseDown property to the MouseEvent class. --- Terminal.Gui/Application.cs | 27 ++++++++++++++++++++++----- Terminal.Gui/Input/Mouse.cs | 5 +++++ UnitTests/Application/MouseTests.cs | 12 ++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index ba3d3f15fc..2a8619217c 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -145,6 +145,7 @@ internal static void ResetState () _mouseEnteredView = null; _lastViewButtonPressed = null; _canProcessClickedEvent = true; + _isMouseDown = false; WantContinuousButtonPressedView = null; MouseEvent = null; GrabbedMouse = null; @@ -1436,6 +1437,7 @@ private static void OnUnGrabbedMouse (View view) internal static View? _mouseEnteredView; internal static View? _lastViewButtonPressed; internal static bool _canProcessClickedEvent = true; + internal static bool? _isMouseDown; /// Event fired when a mouse move or click occurs. Coordinates are screen relative. /// @@ -1468,21 +1470,33 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if (_lastViewButtonPressed is null && mouseEvent.Flags is MouseFlags.Button1Pressed or MouseFlags.Button2Pressed or MouseFlags.Button3Pressed or MouseFlags.Button4Pressed) { _lastViewButtonPressed = view; + _isMouseDown = true; } - else if (_lastViewButtonPressed is { } && _lastViewButtonPressed != view && mouseEvent.Flags is MouseFlags.Button1Released or MouseFlags.Button2Released or MouseFlags.Button3Released or MouseFlags.Button4Released) + else if (_lastViewButtonPressed is { } && mouseEvent.Flags is MouseFlags.Button1Released or MouseFlags.Button2Released or MouseFlags.Button3Released or MouseFlags.Button4Released) { + if (_lastViewButtonPressed != view) + { + _canProcessClickedEvent = false; + } + _lastViewButtonPressed = null; - _canProcessClickedEvent = false; + _isMouseDown = false; } else if (!_canProcessClickedEvent && mouseEvent.Flags is MouseFlags.Button1Clicked or MouseFlags.Button2Clicked or MouseFlags.Button3Clicked or MouseFlags.Button4Clicked) { _canProcessClickedEvent = true; + _isMouseDown = null; return; } else if (!mouseEvent.Flags.HasFlag(MouseFlags.ReportMousePosition)) { _lastViewButtonPressed = null; + _isMouseDown = null; + } + else + { + _isMouseDown = null; } MouseEvent?.Invoke (null, mouseEvent); @@ -1504,7 +1518,8 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Y = boundsLoc.Y, Flags = mouseEvent.Flags, ScreenPosition = new (mouseEvent.X, mouseEvent.Y), - View = view ?? MouseGrabView + View = view ?? MouseGrabView, + IsMouseDown = _isMouseDown }; if (MouseGrabView.Bounds.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false) @@ -1576,7 +1591,8 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) Y = frameLoc.Y, Flags = mouseEvent.Flags, ScreenPosition = new (mouseEvent.X, mouseEvent.Y), - View = view + View = view, + IsMouseDown = _isMouseDown }; } else if (view.BoundsToScreen (view.Bounds).Contains (mouseEvent.X, mouseEvent.Y)) @@ -1589,7 +1605,8 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) Y = boundsPoint.Y, Flags = mouseEvent.Flags, ScreenPosition = new (mouseEvent.X, mouseEvent.Y), - View = view + View = view, + IsMouseDown = _isMouseDown }; } diff --git a/Terminal.Gui/Input/Mouse.cs b/Terminal.Gui/Input/Mouse.cs index 2b3f2852be..8094bd8e50 100644 --- a/Terminal.Gui/Input/Mouse.cs +++ b/Terminal.Gui/Input/Mouse.cs @@ -143,6 +143,11 @@ public class MouseEvent /// public Point ScreenPosition { get; set; } + /// + /// Indicates if the current mouse event has first pressed , latest released or none . + /// + public bool? IsMouseDown { get; set; } + /// Returns a that represents the current . /// A that represents the current . public override string ToString () { return $"({X},{Y}):{Flags}"; } diff --git a/UnitTests/Application/MouseTests.cs b/UnitTests/Application/MouseTests.cs index eda1904c0b..f5f2d187be 100644 --- a/UnitTests/Application/MouseTests.cs +++ b/UnitTests/Application/MouseTests.cs @@ -389,6 +389,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view1); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); + Assert.True (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -396,6 +397,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); + Assert.Null (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -403,6 +405,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.False (Application._canProcessClickedEvent); + Assert.False (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -410,6 +413,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); + Assert.Null (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -417,6 +421,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Equal (Application._lastViewButtonPressed, view2); Assert.True (Application._canProcessClickedEvent); + Assert.True (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -424,6 +429,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view1); Assert.Equal (Application._lastViewButtonPressed, view2); Assert.True (Application._canProcessClickedEvent); + Assert.Null (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -431,6 +437,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); + Assert.False (Application._isMouseDown); Assert.False (view1Clicked); Assert.False (view2Clicked); @@ -438,6 +445,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); + Assert.Null (Application._isMouseDown); Assert.False (view1Clicked); Assert.True (view2Clicked); @@ -445,6 +453,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view1); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); + Assert.True (Application._isMouseDown); Assert.False (view1Clicked); Assert.True (view2Clicked); @@ -452,6 +461,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view2); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); + Assert.Null (Application._isMouseDown); Assert.False (view1Clicked); Assert.True (view2Clicked); @@ -459,6 +469,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view1); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); + Assert.False (Application._isMouseDown); Assert.False (view1Clicked); Assert.True (view2Clicked); @@ -466,6 +477,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.Equal (Application._mouseEnteredView, view1); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); + Assert.Null (Application._isMouseDown); Assert.True (view1Clicked); Assert.True (view2Clicked); } From b20c111e1dd0154c85a7f7e7c449d79aaf8e9761 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 9 Apr 2024 19:28:20 +0100 Subject: [PATCH 07/11] Add MouseDown, MouseMove, MouseUp, MouseDoubleClick and MouseTripleClick. --- Terminal.Gui/View/ViewMouse.cs | 202 ++++++++++++++++++++++++++++++--- UnitTests/View/MouseTests.cs | 46 ++++++++ 2 files changed, 234 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index 0938516b92..c91eaf4dae 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -256,6 +256,30 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) return mouseEvent.Handled = true; } + if (mouseEvent.IsMouseDown == true) + { + if (OnMouseDown (new (mouseEvent))) + { + return true; + } + } + + if (mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition)) + { + if (OnMouseMove (new (mouseEvent))) + { + return true; + } + } + + if (mouseEvent.IsMouseDown == false) + { + if (OnMouseUp (new (mouseEvent))) + { + return true; + } + } + if (HighlightStyle != Gui.HighlightStyle.None || WantContinuousButtonPressed) { if (HandlePressed (mouseEvent)) @@ -278,20 +302,30 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) || mouseEvent.Flags.HasFlag (MouseFlags.Button2Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4Clicked) - || mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked) + ) + { + // If it's a click, and we didn't handle it, then we'll call OnMouseClick + // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and + // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked + return OnMouseClick (new (mouseEvent)); + } + + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button2DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3DoubleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4DoubleClicked) - || mouseEvent.Flags.HasFlag (MouseFlags.Button1TripleClicked) + ) + { + return OnMouseDoubleClick (new (mouseEvent)); + } + + if (mouseEvent.Flags.HasFlag (MouseFlags.Button1TripleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button2TripleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button3TripleClicked) || mouseEvent.Flags.HasFlag (MouseFlags.Button4TripleClicked) ) { - // If it's a click, and we didn't handle it, then we'll call OnMouseClick - // We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and - // it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked - return OnMouseClick (new (mouseEvent)); + return OnMouseTripleClick (new (mouseEvent)); } return false; @@ -527,7 +561,7 @@ internal bool SetHighlight (HighlightStyle style) return args.Cancel; } - /// Called when a mouse event occurs within the view's . + /// Called when any mouse event occurs within the view's . /// /// /// The coordinates are relative to . @@ -552,11 +586,94 @@ protected internal virtual bool OnMouseEvent (MouseEvent mouseEvent) /// public event EventHandler MouseEvent; - /// Invokes the MouseClick event. + /// Called when the user first presses the button down over a view's . + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseDown (MouseEventEventArgs args) + { + MouseDown?.Invoke (this, args); + + return args.Handled; + } + + /// Event fired when the user first presses the button down over a view. + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseDown; + + /// + /// Called when the user moves the mouse over a view, or if mouse was grabbed by the view. + /// Flags will indicate if a button is down. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseMove (MouseEventEventArgs args) + { + MouseMove?.Invoke (this, args); + + return args.Handled; + } + + /// + /// Event fired when the user moves the mouse over a view, or if mouse was grabbed by the view. + /// Flags will indicate if a button is down. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseMove; + + /// + /// Called when the user lets go of the mouse button. Only received if the mouse is over the view, + /// or it was grabbed by the view. + /// /// /// - /// Called when the mouse is either clicked or double-clicked. Check - /// to see which button was clicked. + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseUp (MouseEventEventArgs args) + { + MouseUp?.Invoke (this, args); + + return args.Handled; + } + + /// + /// Event fired when the user lets go of the mouse button. Only received if the mouse is over the view, + /// or it was grabbed by the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseUp; + + /// + /// Called when the user presses down and then releases the mouse over a view (they could move off in between). + /// If they press and release multiple times in quick succession this event will be called for each up action. + /// + /// + /// + /// The coordinates are relative to . /// /// /// , if the event was handled, otherwise. @@ -586,15 +703,72 @@ protected bool OnMouseClick (MouseEventEventArgs args) return args.Handled; } - /// Event fired when a mouse click occurs. + /// + /// Event fired when the user presses down and then releases the mouse over a view (they could move off in between). + /// If they press and release multiple times in quick succession this event will be called for each up action. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseClick; + + /// + /// Called when the user presses and releases the mouse button twice in quick succession without + /// moving the mouse outside the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseDoubleClick (MouseEventEventArgs args) + { + MouseDoubleClick?.Invoke (this, args); + + return args.Handled; + } + + /// + /// Event fired when the user presses and releases the mouse button twice in quick succession without + /// moving the mouse outside the view. + /// /// /// - /// Fired when the mouse is either clicked or double-clicked. Check - /// to see which button was clicked. + /// The coordinates are relative to . /// + /// + public event EventHandler MouseDoubleClick; + + /// + /// Called when the user presses and releases the mouse button thrice in quick succession without + /// moving the mouse outside the view. + /// + /// /// /// The coordinates are relative to . /// /// - public event EventHandler MouseClick; + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseTripleClick (MouseEventEventArgs args) + { + MouseTripleClick?.Invoke (this, args); + + return args.Handled; + } + + /// + /// Event fired when the user presses and releases the mouse button thrice in quick succession without + /// moving the mouse outside the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseTripleClick; } diff --git a/UnitTests/View/MouseTests.cs b/UnitTests/View/MouseTests.cs index 339e738784..a8f70f1ac1 100644 --- a/UnitTests/View/MouseTests.cs +++ b/UnitTests/View/MouseTests.cs @@ -583,4 +583,50 @@ void View_Highlight (object sender, HighlightEventArgs e) } } + [Fact] + [AutoInitShutdown] + public void Test_All_Mouse_Events () + { + var onMouseEvent = false; + var onMouseDown = false; + var onMouseMove = false; + var onMouseUp = false; + var onMouseClick = false; + var onMouseDoubleClick = false; + var onMouseTripleClick = false; + + var view = new View { CanFocus = true, Width = 10, Height = 1}; + view.MouseEvent += (s, e) => onMouseEvent = true; + view.MouseDown += (s, e) => onMouseDown = true; + view.MouseMove += (s, e) => onMouseMove = true; + view.MouseUp += (s, e) => onMouseUp = true; + view.MouseClick += (s, e) => onMouseClick = true; + view.MouseDoubleClick += (s, e) => onMouseDoubleClick = true; + view.MouseTripleClick += (s, e) => onMouseTripleClick = true; + + var top = new Toplevel (); + top.Add (view); + Application.Begin (top); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.WheeledDown }); + Assert.True (onMouseEvent); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Assert.True (onMouseDown); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Assert.True (onMouseMove); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Released }); + Assert.True (onMouseUp); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked }); + Assert.True (onMouseClick); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1DoubleClicked }); + Assert.True (onMouseDoubleClick); + + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1TripleClicked }); + Assert.True (onMouseTripleClick); + } } From c22902be2149f7766646dd61df5002d211c65142 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 2 May 2024 15:17:42 +0100 Subject: [PATCH 08/11] WantMousePositionReports and WantContinuousButtonPressed are special flags for MouseGrabView. --- Terminal.Gui/Application.cs | 25 +++++----------- Terminal.Gui/View/Adornment/Border.cs | 1 + UnitTests/Views/ButtonTests.cs | 43 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index aa68f091ca..5cd1231ee1 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -1642,25 +1642,16 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false) { // The mouse has moved outside the bounds of the view that grabbed the mouse - // Give a chance for the current view process the event - if (ProcessMouseEvent (mouseEvent, view)) - { - return; - } + _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent); } - //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) + if ((MouseGrabView?.WantMousePositionReports == true || MouseGrabView?.WantContinuousButtonPressed == true) + && MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) { return; } } - ProcessMouseEvent (mouseEvent, view); - } - - private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) - { if (view is { WantContinuousButtonPressed: true }) { WantContinuousButtonPressedView = view; @@ -1670,7 +1661,6 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) WantContinuousButtonPressedView = null; } - if (view is not Adornment) { if ((view is null || view == OverlappedTop) @@ -1693,7 +1683,7 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) if (view is null) { - return false; + return; } MouseEvent? me = null; @@ -1729,7 +1719,7 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) if (me is null) { - return false; + return; } if (_mouseEnteredView is null) @@ -1746,7 +1736,7 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition) { - return false; + return; } WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null; @@ -1787,9 +1777,8 @@ private static bool ProcessMouseEvent (MouseEvent mouseEvent, View? view) } BringOverlappedTopToFront (); - - return true; } + #nullable restore #endregion Mouse handling diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 922fa2a19f..9c55bcc453 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -59,6 +59,7 @@ public Border (View parent) : base (parent) HighlightStyle |= HighlightStyle.Pressed; Highlight += Border_Highlight; + WantMousePositionReports = true; } #if SUBVIEW_BASED_BORDER diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 3d37a8f7ee..d3cb6abaa1 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -823,7 +823,50 @@ public void WantContinuousButtonPressed_True_ButtonPressRelease_Accepts (MouseFl button.NewMouseEvent (me); Assert.Equal (1, acceptCount); + me.Flags = clicked; + button.NewMouseEvent (me); + Assert.Equal (1, acceptCount); + button.Dispose (); } + [Fact] + [AutoInitShutdown] + public void WantMousePositionReports_False_And_WantContinuousButtonPressed_False_Focus_The_Another_Button_On_Mouse_Pressed_Released () + { + var button1 = new Button { Text = "Button1" }; + var acceptCount = 0; + button1.Accept += (s, e) => acceptCount++; + var button2 = new Button { Text = "Button2", X = Pos.Right (button1) + 1 }; + button2.Accept += (s, e) => acceptCount++; + var top = new Toplevel (); + top.Add (button1, button2); + Application.Begin (top); + + Assert.True (button1.HasFocus); + Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Assert.Equal (button1, Application.MouseGrabView); + Assert.Equal (0, acceptCount); + Assert.True (button1.HasFocus); + + Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition}); + Assert.Equal (button2, Application.MouseGrabView); + Assert.Equal (0, acceptCount); + Assert.True (button2.HasFocus); + + Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Released }); + Assert.Equal (button2, Application.MouseGrabView); + Assert.Equal (0, acceptCount); + Assert.True (button2.HasFocus); + + Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Clicked }); + Assert.Equal (button2, Application.MouseGrabView); + Assert.Equal (0, acceptCount); + Assert.True (button2.HasFocus); + + Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Clicked }); + Assert.Null (Application.MouseGrabView); + Assert.Equal (1, acceptCount); + Assert.True (button2.HasFocus); + } } \ No newline at end of file From 7ba3d30fd40f6b0a9da0e1d75ebce77da07c39df Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 9 Jun 2024 19:52:56 +0100 Subject: [PATCH 09/11] Fix merge errors. --- Terminal.Gui/Application.cs | 6 +++--- UnitTests/Application/MouseTests.cs | 26 ++++++++++++++------------ UnitTests/View/MouseTests.cs | 16 +++++++++------- UnitTests/Views/ButtonTests.cs | 16 +++++++--------- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 31635ce3cd..1d2a8d0a30 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -1617,7 +1617,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = MouseGrabView + View = MouseGrabView, IsMouseDown = _isMouseDown }; @@ -1679,7 +1679,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = view + View = view, IsMouseDown = _isMouseDown }; } @@ -1692,7 +1692,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Position = viewportLocation, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = view + View = view, IsMouseDown = _isMouseDown }; } diff --git a/UnitTests/Application/MouseTests.cs b/UnitTests/Application/MouseTests.cs index 32e9a5ff0b..2cb9e0836e 100644 --- a/UnitTests/Application/MouseTests.cs +++ b/UnitTests/Application/MouseTests.cs @@ -415,7 +415,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () top.Add (view1, view2); Application.Begin (top); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed }); Assert.Equal (Application._mouseEnteredView, view1); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); @@ -423,7 +423,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); @@ -431,7 +431,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Released }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Released }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.False (Application._canProcessClickedEvent); @@ -439,7 +439,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Clicked }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); @@ -447,7 +447,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Pressed }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Pressed }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Equal (Application._lastViewButtonPressed, view2); Assert.True (Application._canProcessClickedEvent); @@ -455,7 +455,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (Application._mouseEnteredView, view1); Assert.Equal (Application._lastViewButtonPressed, view2); Assert.True (Application._canProcessClickedEvent); @@ -463,7 +463,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Released }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Released }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); @@ -471,7 +471,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.False (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Clicked }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); @@ -479,7 +479,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.True (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed }); Assert.Equal (Application._mouseEnteredView, view1); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); @@ -487,7 +487,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.True (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 2, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Application.OnMouseEvent (new () { Position = new (0, 2), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.Equal (Application._mouseEnteredView, view2); Assert.Equal (Application._lastViewButtonPressed, view1); Assert.True (Application._canProcessClickedEvent); @@ -495,7 +495,7 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.True (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Released }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Released }); Assert.Equal (Application._mouseEnteredView, view1); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); @@ -503,13 +503,15 @@ public void Clicked_Event_Only_Occurs_In_The_Same_View_That_Pressed_The_Mouse () Assert.False (view1Clicked); Assert.True (view2Clicked); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }); Assert.Equal (Application._mouseEnteredView, view1); Assert.Null (Application._lastViewButtonPressed); Assert.True (Application._canProcessClickedEvent); Assert.Null (Application._isMouseDown); Assert.True (view1Clicked); Assert.True (view2Clicked); + + top.Dispose (); } #endregion diff --git a/UnitTests/View/MouseTests.cs b/UnitTests/View/MouseTests.cs index b36a31b87d..779206095b 100644 --- a/UnitTests/View/MouseTests.cs +++ b/UnitTests/View/MouseTests.cs @@ -671,25 +671,27 @@ public void Test_All_Mouse_Events () top.Add (view); Application.Begin (top); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.WheeledDown }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.WheeledDown }); Assert.True (onMouseEvent); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed }); Assert.True (onMouseDown); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }); Assert.True (onMouseMove); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Released }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Released }); Assert.True (onMouseUp); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked }); Assert.True (onMouseClick); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1DoubleClicked }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1DoubleClicked }); Assert.True (onMouseDoubleClick); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1TripleClicked }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1TripleClicked }); Assert.True (onMouseTripleClick); + + top.Dispose (); } } diff --git a/UnitTests/Views/ButtonTests.cs b/UnitTests/Views/ButtonTests.cs index 5392e9cc66..248caba9fb 100644 --- a/UnitTests/Views/ButtonTests.cs +++ b/UnitTests/Views/ButtonTests.cs @@ -593,10 +593,6 @@ public void WantContinuousButtonPressed_True_ButtonPressRelease_Accepts (MouseFl button.NewMouseEvent (me); Assert.Equal (1, acceptCount); - me.Flags = clicked; - button.NewMouseEvent (me); - Assert.Equal (1, acceptCount); - button.Dispose (); } @@ -614,29 +610,31 @@ public void WantMousePositionReports_False_And_WantContinuousButtonPressed_False Application.Begin (top); Assert.True (button1.HasFocus); - Application.OnMouseEvent (new () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }); + Application.OnMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Pressed }); Assert.Equal (button1, Application.MouseGrabView); Assert.Equal (0, acceptCount); Assert.True (button1.HasFocus); - Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition}); + Application.OnMouseEvent (new () { Position = new (13, 0), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition}); Assert.Equal (button2, Application.MouseGrabView); Assert.Equal (0, acceptCount); Assert.True (button2.HasFocus); - Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Released }); + Application.OnMouseEvent (new () { Position = new (13, 0), Flags = MouseFlags.Button1Released }); Assert.Equal (button2, Application.MouseGrabView); Assert.Equal (0, acceptCount); Assert.True (button2.HasFocus); - Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (13, 0), Flags = MouseFlags.Button1Clicked }); Assert.Equal (button2, Application.MouseGrabView); Assert.Equal (0, acceptCount); Assert.True (button2.HasFocus); - Application.OnMouseEvent (new () { X = 13, Y = 0, Flags = MouseFlags.Button1Clicked }); + Application.OnMouseEvent (new () { Position = new (13, 0), Flags = MouseFlags.Button1Clicked }); Assert.Null (Application.MouseGrabView); Assert.Equal (1, acceptCount); Assert.True (button2.HasFocus); + + top.Dispose (); } } \ No newline at end of file From 5c5cd5189431495863df993f1c88147cc8a1deea Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 9 Jun 2024 23:50:14 +0100 Subject: [PATCH 10/11] Fix merge errors. --- Terminal.Gui/Application/ApplicationMouse.cs | 47 ++++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/Application/ApplicationMouse.cs b/Terminal.Gui/Application/ApplicationMouse.cs index 9f2a953390..ee5782f4e4 100644 --- a/Terminal.Gui/Application/ApplicationMouse.cs +++ b/Terminal.Gui/Application/ApplicationMouse.cs @@ -114,6 +114,9 @@ private static void OnUnGrabbedMouse (View view) // Used by OnMouseEvent to track the last view that was clicked on. internal static View? _mouseEnteredView; + internal static View? _lastViewButtonPressed; + internal static bool _canProcessClickedEvent = true; + internal static bool? _isMouseDown; /// Event fired when a mouse move or click occurs. Coordinates are screen relative. /// @@ -142,6 +145,38 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) mouseEvent.View = view; } + if (_lastViewButtonPressed is null && mouseEvent.Flags is MouseFlags.Button1Pressed or MouseFlags.Button2Pressed or MouseFlags.Button3Pressed or MouseFlags.Button4Pressed) + { + _lastViewButtonPressed = view; + _isMouseDown = true; + } + else if (_lastViewButtonPressed is { } && mouseEvent.Flags is MouseFlags.Button1Released or MouseFlags.Button2Released or MouseFlags.Button3Released or MouseFlags.Button4Released) + { + if (_lastViewButtonPressed != view) + { + _canProcessClickedEvent = false; + } + + _lastViewButtonPressed = null; + _isMouseDown = false; + } + else if (!_canProcessClickedEvent && mouseEvent.Flags is MouseFlags.Button1Clicked or MouseFlags.Button2Clicked or MouseFlags.Button3Clicked or MouseFlags.Button4Clicked) + { + _canProcessClickedEvent = true; + _isMouseDown = null; + + return; + } + else if (!mouseEvent.Flags.HasFlag (MouseFlags.ReportMousePosition)) + { + _lastViewButtonPressed = null; + _isMouseDown = null; + } + else + { + _isMouseDown = null; + } + MouseEvent?.Invoke (null, mouseEvent); if (mouseEvent.Handled) @@ -160,7 +195,8 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = MouseGrabView + View = MouseGrabView, + IsMouseDown = _isMouseDown }; if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false) @@ -170,7 +206,8 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) } //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); - if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) + if ((MouseGrabView?.WantMousePositionReports == true || MouseGrabView?.WantContinuousButtonPressed == true) + && MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true) { return; } @@ -221,7 +258,8 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = view + View = view, + IsMouseDown = _isMouseDown }; } else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position)) @@ -233,7 +271,8 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) Position = viewportLocation, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = view + View = view, + IsMouseDown = _isMouseDown }; } From 4ccc08c87aba76da1f51c60dbfcf366e1fe810c1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 11 Jul 2024 13:56:31 +0100 Subject: [PATCH 11/11] Fix merge errors. --- Terminal.Gui/View/ViewMouse.cs | 149 ++++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 4 deletions(-) diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index df0ef580ae..aa190fcf0f 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -31,6 +31,25 @@ public partial class View /// public event EventHandler MouseClick; + /// + /// Event fired when the user presses and releases the mouse button twice in quick succession without + /// moving the mouse outside the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseDoubleClick; + + /// Event fired when the user first presses the button down over a view. + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseDown; + /// Event fired when the mouse moves into the View's . public event EventHandler MouseEnter; @@ -45,6 +64,39 @@ public partial class View /// Event fired when the mouse leaves the View's . public event EventHandler MouseLeave; + /// + /// Event fired when the user moves the mouse over a view, or if mouse was grabbed by the view. + /// Flags will indicate if a button is down. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseMove; + + /// + /// Event fired when the user presses and releases the mouse button thrice in quick succession without + /// moving the mouse outside the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseTripleClick; + + /// + /// Event fired when the user lets go of the mouse button. Only received if the mouse is over the view, + /// or it was grabbed by the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + public event EventHandler MouseUp; + /// /// Processes a . This method is called by when a mouse /// event occurs. @@ -169,6 +221,39 @@ public partial class View /// if mouse position reports are wanted; otherwise, . public virtual bool WantMousePositionReports { get; set; } + /// + /// Called when the user presses and releases the mouse button twice in quick succession without + /// moving the mouse outside the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseDoubleClick (MouseEventEventArgs args) + { + MouseDoubleClick?.Invoke (this, args); + + return args.Handled; + } + + /// Called when the user first presses the button down over a view's . + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseDown (MouseEventEventArgs args) + { + MouseDown?.Invoke (this, args); + + return args.Handled; + } + /// /// Called by when the mouse enters . The view will /// then receive mouse events until is called indicating the mouse has left @@ -241,6 +326,60 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) return args.Handled; } + /// + /// Called when the user moves the mouse over a view, or if mouse was grabbed by the view. + /// Flags will indicate if a button is down. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseMove (MouseEventEventArgs args) + { + MouseMove?.Invoke (this, args); + + return args.Handled; + } + + /// + /// Called when the user presses and releases the mouse button thrice in quick succession without + /// moving the mouse outside the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseTripleClick (MouseEventEventArgs args) + { + MouseTripleClick?.Invoke (this, args); + + return args.Handled; + } + + /// + /// Called when the user lets go of the mouse button. Only received if the mouse is over the view, + /// or it was grabbed by the view. + /// + /// + /// + /// The coordinates are relative to . + /// + /// + /// + /// , if the event was handled, otherwise. + protected internal virtual bool OnMouseUp (MouseEventEventArgs args) + { + MouseUp?.Invoke (this, args); + + return args.Handled; + } + /// /// Called when the view is to be highlighted. /// @@ -265,11 +404,13 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) return args.Cancel; } - /// Invokes the MouseClick event. + /// + /// Called when the user presses down and then releases the mouse over a view (they could move off in between). + /// If they press and release multiple times in quick succession this event will be called for each up action. + /// /// /// - /// Called when the mouse is either clicked or double-clicked. Check - /// to see which button was clicked. + /// The coordinates are relative to . /// /// /// , if the event was handled, otherwise. @@ -476,7 +617,7 @@ internal bool SetHighlight (HighlightStyle newHighlightStyle) // Enable override via virtual method and/or event HighlightStyle copy = HighlightStyle; - var args = new CancelEventArgs (ref copy, ref newHighlightStyle); + CancelEventArgs args = new CancelEventArgs (ref copy, ref newHighlightStyle); if (OnHighlight (args) == true) {