From e95ff61fef6b8a078e529116a1a4f5be8b958d7f Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 21 Sep 2024 15:46:05 -0600 Subject: [PATCH] Refactored Enter event and added unit tests --- Terminal.Gui/Application/Application.Mouse.cs | 21 +- Terminal.Gui/View/Adornment/Adornment.cs | 33 +- Terminal.Gui/View/View.Mouse.cs | 165 ++--- Terminal.Gui/Views/ScrollView.cs | 4 +- UICatalog/Scenarios/Mouse.cs | 3 +- .../Mouse/ApplicationMouseEnterLeaveTests.cs | 576 ++++++++++++++++++ ...MouseTests.cs => ApplicationMouseTests.cs} | 6 +- .../Application/Mouse/MouseEnterLeaveTests.cs | 337 ---------- UnitTests/Input/ResponderTests.cs | 2 - .../View/Mouse/GetViewsUnderMouseTests.cs | 287 +++++---- UnitTests/View/Mouse/MouseEnterLeaveTests.cs | 129 ++-- UnitTests/View/ViewTests.cs | 2 - 12 files changed, 923 insertions(+), 642 deletions(-) create mode 100644 UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs rename UnitTests/Application/Mouse/{MouseTests.cs => ApplicationMouseTests.cs} (99%) delete mode 100644 UnitTests/Application/Mouse/MouseEnterLeaveTests.cs diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index 3cce3e5da..3db58fc23 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -1,4 +1,5 @@ #nullable enable +using System.ComponentModel; using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.VisualBasic.Syntax; @@ -344,20 +345,26 @@ public static partial class Application // Mouse handling _cachedViewsUnderMouse.Add (view); - if (view is Adornment adornmentView) + bool raise = false; + if (view is Adornment { Parent: { } } adornmentView) { Point frameLoc = view.ScreenToFrame (me.ScreenPosition); - if (adornmentView.Parent is { } && !adornmentView.Contains (frameLoc)) - { - view.NewMouseEnterEvent (me); - } + raise = adornmentView.Contains (frameLoc); } else { Point superViewLoc = view.SuperView?.ScreenToViewport (me.ScreenPosition) ?? me.ScreenPosition; - if (view.Contains (superViewLoc)) + raise = view.Contains (superViewLoc); + } + + if (raise) + { + CancelEventArgs eventArgs = new (); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); + + if (cancelled is true || eventArgs.Cancel) { - view.NewMouseEnterEvent (me); + break; } } } diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index f9a9db3ef..af16c1b94 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -1,4 +1,5 @@ #nullable enable +using System.ComponentModel; using Terminal.Gui; using Attribute = Terminal.Gui.Attribute; @@ -227,37 +228,5 @@ public class Adornment : View return Thickness.Contains (frame, location); } - /// - protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) - { - //// Invert Normal - //if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null) - //{ - // var cs = new ColorScheme (ColorScheme) - // { - // Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground) - // }; - // ColorScheme = cs; - //} - - return base.OnMouseEnter (mouseEvent); - } - - /// - protected internal override bool OnMouseLeave (MouseEvent mouseEvent) - { - //// Invert Normal - //if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null) - //{ - // var cs = new ColorScheme (ColorScheme) - // { - // Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground) - // }; - // ColorScheme = cs; - //} - - return base.OnMouseLeave (mouseEvent); - } - #endregion Mouse Support } diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs index b86922ac0..fdfd17286 100644 --- a/Terminal.Gui/View/View.Mouse.cs +++ b/Terminal.Gui/View/View.Mouse.cs @@ -31,9 +31,6 @@ public partial class View // Mouse APIs /// public event EventHandler? MouseClick; - /// Event fired when the mouse moves into the View's . - public event EventHandler? MouseEnter; - /// Event fired when a mouse event occurs. /// /// @@ -140,25 +137,100 @@ public partial class View // Mouse APIs /// if mouse position reports are wanted; otherwise, . public virtual bool WantMousePositionReports { get; set; } + #region MouseEnterLeave + /// - /// Called by when the mouse enters . The view will - /// then receive mouse events until is called indicating the mouse has left - /// the view. + /// INTERNAL Called by when the mouse moves over the View's . + /// will + /// be raised when the mouse is no longer over the . If another View occludes this View, the + /// that View will also receive MouseEnter/Leave events. + /// + /// + /// + /// if the event was canceled, if not, if the + /// view is not visible. Cancelling the event + /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. + /// + internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs) + { + if (!CanBeVisible (this)) + { + return null; + } + + if (OnMouseEnter (eventArgs)) + { + return true; + } + + MouseEnter?.Invoke (this, eventArgs); + +#if HOVER + if (HighlightStyle.HasFlag(HighlightStyle.Hover)) + { + if (SetHighlight (HighlightStyle.Hover)) + { + return true; + } + } +#endif + + return eventArgs.Cancel; + } + + /// + /// Called when the mouse moves over the View's and no other non-Subview occludes it. will + /// be raised when the mouse is no longer over the . /// /// /// - /// Override this method or subscribe to to change the default enter behavior. + /// A view must be visible to receive Enter events (Leave events are always received). /// /// - /// The coordinates are relative to . + /// If the event is cancelled, the mouse event will not be propagated to other views and + /// will not be raised. + /// + /// + /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . + /// + /// + /// See for more information. /// /// - /// - /// , if the event was handled, otherwise. - protected internal virtual bool? OnMouseEnter (MouseEvent mouseEvent) - { - return false; - } + /// + /// + /// if the event was canceled, if not. Cancelling the event + /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. + /// + protected virtual bool OnMouseEnter (CancelEventArgs eventArgs) { return false; } + + /// + /// Raised when the mouse moves over the View's . will + /// be raised when the mouse is no longer over the . If another View occludes this View, the + /// that View will also receive MouseEnter/Leave events. + /// + /// + /// + /// A view must be visible to receive Enter events (Leave events are always received). + /// + /// + /// If the event is cancelled, the mouse event will not be propagated to other views. + /// + /// + /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . + /// + /// + /// Set to if the event was canceled, + /// if not. Cancelling the event + /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. + /// + /// + /// See for more information. + /// + /// + public event EventHandler? MouseEnter; + + #endregion MouseEnterLeave /// Called when a mouse event occurs within the view's . /// @@ -191,10 +263,7 @@ public partial class View // Mouse APIs /// /// /// , if the event was handled, otherwise. - protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) - { - return false; - } + protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; } /// /// Called when the view is to be highlighted. @@ -316,61 +385,6 @@ public partial class View // Mouse APIs return false; } - /// - /// INTERNAL Called by when the mouse moves over the View's . will - /// be raised when the mouse is no longer over the . If another View occludes this View, the - /// that View will also receive MouseEnter/Leave events. - /// - /// - /// - /// A view must be visible to receive Enter events (Leave events are always received). - /// - /// - /// This method calls to raise the event. - /// - /// - /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's . - /// - /// - /// See for more information. - /// - /// - /// - /// if the event was handled, otherwise. Handling the event - /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events. - internal bool? NewMouseEnterEvent (MouseEvent mouseEvent) - { - if (!Enabled) - { - return false; - } - - if (!CanBeVisible (this)) - { - return false; - } - - if (OnMouseEnter (mouseEvent) == true) - { - return true; - } - - var args = new MouseEventEventArgs (mouseEvent); - MouseEnter?.Invoke (this, args); - -#if HOVER - if (HighlightStyle.HasFlag(HighlightStyle.Hover)) - { - if (SetHighlight (HighlightStyle.Hover)) - { - return true; - } - } -#endif - - return args.Handled; - } - /// /// INTERNAL Called by when the mouse leaves . /// @@ -435,7 +449,7 @@ public partial class View // Mouse APIs // Enable override via virtual method and/or event HighlightStyle copy = HighlightStyle; - var args = new CancelEventArgs (ref copy, ref newHighlightStyle); + CancelEventArgs args = new (ref copy, ref newHighlightStyle); if (OnHighlight (args) == true) { @@ -565,7 +579,7 @@ public partial class View // Mouse APIs } /// - /// INTERNAL: Gets the Views that are under the mouse at , including Adornments. + /// INTERNAL: Gets the Views that are under the mouse at , including Adornments. /// /// /// @@ -636,5 +650,4 @@ public partial class View // Mouse APIs return viewsUnderMouse; } - } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 43de42db5..2b5ca2855 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -11,6 +11,8 @@ // - Raise events // - Perhaps allow an option to not display the scrollbar arrow indicators? +using System.ComponentModel; + namespace Terminal.Gui; /// @@ -743,7 +745,7 @@ public class ScrollView : View } } - private void View_MouseEnter (object sender, MouseEventEventArgs e) { Application.GrabMouse (this); } + private void View_MouseEnter (object sender, CancelEventArgs e) { Application.GrabMouse (this); } private void View_MouseLeave (object sender, MouseEventEventArgs e) { diff --git a/UICatalog/Scenarios/Mouse.cs b/UICatalog/Scenarios/Mouse.cs index 8f14774a5..ea9eab6d5 100644 --- a/UICatalog/Scenarios/Mouse.cs +++ b/UICatalog/Scenarios/Mouse.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; using Terminal.Gui; @@ -245,7 +246,7 @@ public class Mouse : Scenario Padding.MouseEnter += PaddingOnMouseEnter; Padding.MouseLeave += PaddingOnMouseLeave; - void PaddingOnMouseEnter (object o, MouseEventEventArgs mouseEventEventArgs) + void PaddingOnMouseEnter (object o, CancelEventArgs e) { Padding.ColorScheme = Colors.ColorSchemes ["Error"]; } diff --git a/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs b/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs new file mode 100644 index 000000000..5b8cb1fb8 --- /dev/null +++ b/UnitTests/Application/Mouse/ApplicationMouseEnterLeaveTests.cs @@ -0,0 +1,576 @@ +using System.ComponentModel; + +namespace Terminal.Gui.ViewMouseTests; + +[Trait ("Category", "Input")] +public class ApplicationMouseEnterLeaveTests +{ + private class TestView : View + { + public TestView () + { + X = 1; + Y = 1; + Width = 1; + Height = 1; + } + + public bool CancelOnEnter { get; } + public int OnMouseEnterCalled { get; private set; } + public int OnMouseLeaveCalled { get; private set; } + + protected override bool OnMouseEnter (CancelEventArgs eventArgs) + { + OnMouseEnterCalled++; + eventArgs.Cancel = CancelOnEnter; + + base.OnMouseEnter (eventArgs); + + return eventArgs.Cancel; + } + + protected internal override bool OnMouseLeave (MouseEvent mouseEvent) + { + OnMouseLeaveCalled++; + + base.OnMouseLeave (mouseEvent); + + return mouseEvent.Handled; + } + } + + [Fact] + public void RaiseMouseEnterLeaveEvents_MouseEntersView_CallsOnMouseEnter () + { + // Arrange + Application.Top = new () { Frame = new (0, 0, 10, 10) }; + var view = new TestView (); + Application.Top.Add (view); + var mousePosition = new Point (1, 1); + List currentViewsUnderMouse = new () { view }; + + var mouseEvent = new MouseEvent + { + Position = mousePosition, + ScreenPosition = mousePosition + }; + + Application._cachedViewsUnderMouse.Clear (); + + try + { + // Act + Application.RaiseMouseEnterLeaveEvents (mousePosition, currentViewsUnderMouse, mouseEvent); + + // Assert + Assert.Equal (1, view.OnMouseEnterCalled); + } + finally + { + // Cleanup + Application.Top?.Dispose (); + Application.ResetState (); + } + } + + [Fact] + public void RaiseMouseEnterLeaveEvents_MouseLeavesView_CallsOnMouseLeave () + { + // Arrange + Application.Top = new () { Frame = new (0, 0, 10, 10) }; + var view = new TestView (); + Application.Top.Add (view); + var mousePosition = new Point (0, 0); + List currentViewsUnderMouse = new (); + var mouseEvent = new MouseEvent (); + + Application._cachedViewsUnderMouse.Clear (); + Application._cachedViewsUnderMouse.Add (view); + + try + { + // Act + Application.RaiseMouseEnterLeaveEvents (mousePosition, currentViewsUnderMouse, mouseEvent); + + // Assert + Assert.Equal (0, view.OnMouseEnterCalled); + Assert.Equal (1, view.OnMouseLeaveCalled); + } + finally + { + // Cleanup + Application.Top?.Dispose (); + Application.ResetState (); + } + } + + [Fact] + public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenAdjacentViews_CallsOnMouseEnterAndLeave () + { + // Arrange + Application.Top = new () { Frame = new (0, 0, 10, 10) }; + var view1 = new TestView (); // at 1,1 to 2,2 + + var view2 = new TestView () // at 2,2 to 3,3 + { + X = 2, + Y = 2 + }; + Application.Top.Add (view1); + Application.Top.Add (view2); + + Application._cachedViewsUnderMouse.Clear (); + + try + { + // Act + var mousePosition = new Point (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (0, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (0, view2.OnMouseEnterCalled); + Assert.Equal (0, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (1, 1); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (0, view2.OnMouseEnterCalled); + Assert.Equal (0, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (2, 2); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, view2.OnMouseEnterCalled); + Assert.Equal (0, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (3, 3); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, view2.OnMouseEnterCalled); + Assert.Equal (1, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, view2.OnMouseEnterCalled); + Assert.Equal (1, view2.OnMouseLeaveCalled); + } + finally + { + // Cleanup + Application.Top?.Dispose (); + Application.ResetState (); + } + } + + [Fact] + public void RaiseMouseEnterLeaveEvents_NoViewsUnderMouse_DoesNotCallOnMouseEnterOrLeave () + { + // Arrange + Application.Top = new () { Frame = new (0, 0, 10, 10) }; + var view = new TestView (); + Application.Top.Add (view); + var mousePosition = new Point (0, 0); + List currentViewsUnderMouse = new (); + var mouseEvent = new MouseEvent (); + + Application._cachedViewsUnderMouse.Clear (); + + try + { + // Act + Application.RaiseMouseEnterLeaveEvents (mousePosition, currentViewsUnderMouse, mouseEvent); + + // Assert + Assert.Equal (0, view.OnMouseEnterCalled); + Assert.Equal (0, view.OnMouseLeaveCalled); + } + finally + { + // Cleanup + Application.Top?.Dispose (); + Application.ResetState (); + } + } + + [Fact] + public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingPeerViews_CallsOnMouseEnterAndLeave () + { + // Arrange + Application.Top = new () { Frame = new (0, 0, 10, 10) }; + + var view1 = new TestView + { + Width = 2 + }; // at 1,1 to 3,2 + + var view2 = new TestView () // at 2,2 to 4,3 + { + Width = 2, + X = 2, + Y = 2 + }; + Application.Top.Add (view1); + Application.Top.Add (view2); + + Application._cachedViewsUnderMouse.Clear (); + + try + { + // Act + var mousePosition = new Point (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (0, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (0, view2.OnMouseEnterCalled); + Assert.Equal (0, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (1, 1); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (0, view2.OnMouseEnterCalled); + Assert.Equal (0, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (2, 2); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, view2.OnMouseEnterCalled); + Assert.Equal (0, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (3, 3); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, view2.OnMouseEnterCalled); + Assert.Equal (1, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, view2.OnMouseEnterCalled); + Assert.Equal (1, view2.OnMouseLeaveCalled); + + // Act + mousePosition = new (2, 2); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (2, view2.OnMouseEnterCalled); + Assert.Equal (1, view2.OnMouseLeaveCalled); + } + finally + { + // Cleanup + Application.Top?.Dispose (); + Application.ResetState (); + } + } + + [Fact] + public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingSubViews_CallsOnMouseEnterAndLeave () + { + // Arrange + Application.Top = new () { Frame = new (0, 0, 10, 10) }; + + var view1 = new TestView + { + Width = 2, + Height = 2, + Arrangement = ViewArrangement.Overlapped + }; // at 1,1 to 3,3 (screen) + + var subView = new TestView + { + Width = 2, + Height = 2, + X = 1, + Y = 1, + Arrangement = ViewArrangement.Overlapped + }; // at 2,2 to 4,4 (screen) + view1.Add (subView); + Application.Top.Add (view1); + + Application._cachedViewsUnderMouse.Clear (); + + try + { + Assert.Equal (1, view1.FrameToScreen ().X); + Assert.Equal (2, subView.FrameToScreen ().X); + + // Act + var mousePosition = new Point (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (0, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (0, subView.OnMouseEnterCalled); + Assert.Equal (0, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (1, 1); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (1, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (0, subView.OnMouseEnterCalled); + Assert.Equal (0, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (2, 2); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (2, view1.OnMouseEnterCalled); + Assert.Equal (0, view1.OnMouseLeaveCalled); + Assert.Equal (1, subView.OnMouseEnterCalled); + Assert.Equal (0, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (2, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (1, subView.OnMouseEnterCalled); + Assert.Equal (1, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (2, 2); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (3, view1.OnMouseEnterCalled); + Assert.Equal (1, view1.OnMouseLeaveCalled); + Assert.Equal (2, subView.OnMouseEnterCalled); + Assert.Equal (1, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (3, 3); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (3, view1.OnMouseEnterCalled); + Assert.Equal (2, view1.OnMouseLeaveCalled); + Assert.Equal (2, subView.OnMouseEnterCalled); + Assert.Equal (1, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (0, 0); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (3, view1.OnMouseEnterCalled); + Assert.Equal (2, view1.OnMouseLeaveCalled); + Assert.Equal (2, subView.OnMouseEnterCalled); + Assert.Equal (1, subView.OnMouseLeaveCalled); + + // Act + mousePosition = new (2, 2); + + Application.RaiseMouseEnterLeaveEvents ( + mousePosition, + View.GetViewsUnderMouse (mousePosition), + new() + { + Position = mousePosition, + ScreenPosition = mousePosition + }); + + // Assert + Assert.Equal (4, view1.OnMouseEnterCalled); + Assert.Equal (2, view1.OnMouseLeaveCalled); + Assert.Equal (3, subView.OnMouseEnterCalled); + Assert.Equal (1, subView.OnMouseLeaveCalled); + } + finally + { + // Cleanup + Application.Top?.Dispose (); + Application.ResetState (); + } + } +} diff --git a/UnitTests/Application/Mouse/MouseTests.cs b/UnitTests/Application/Mouse/ApplicationMouseTests.cs similarity index 99% rename from UnitTests/Application/Mouse/MouseTests.cs rename to UnitTests/Application/Mouse/ApplicationMouseTests.cs index a3cf2d584..d25f9adc7 100644 --- a/UnitTests/Application/Mouse/MouseTests.cs +++ b/UnitTests/Application/Mouse/ApplicationMouseTests.cs @@ -4,11 +4,12 @@ namespace Terminal.Gui.ApplicationTests; -public class MouseTests +[Trait ("Category", "Input")] +public class ApplicationMouseTests { private readonly ITestOutputHelper _output; - public MouseTests (ITestOutputHelper output) + public ApplicationMouseTests (ITestOutputHelper output) { _output = output; #if DEBUG_IDISPOSABLE @@ -401,5 +402,6 @@ public class MouseTests Assert.Equal (0, count); top.Dispose (); } + #endregion } diff --git a/UnitTests/Application/Mouse/MouseEnterLeaveTests.cs b/UnitTests/Application/Mouse/MouseEnterLeaveTests.cs deleted file mode 100644 index e6a85e91a..000000000 --- a/UnitTests/Application/Mouse/MouseEnterLeaveTests.cs +++ /dev/null @@ -1,337 +0,0 @@ -namespace Terminal.Gui.ViewMouseTests; - -[Trait ("Category", "Input")] -public class ApplicationMouseEnterLeaveTests -{ - private class TestView : View - { - public TestView () - { - X = 1; - Y = 1; - Width = 1; - Height = 1; - } - - public bool HandleOnEnter { get; } - public bool HandleOnLeave { get; } - - public int OnMouseEnterCalled { get; private set; } - public int OnMouseLeaveCalled { get; private set; } - - protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) - { - OnMouseEnterCalled++; - mouseEvent.Handled = HandleOnEnter; - - base.OnMouseEnter (mouseEvent); - - return mouseEvent.Handled; - } - - protected internal override bool OnMouseLeave (MouseEvent mouseEvent) - { - OnMouseLeaveCalled++; - mouseEvent.Handled = HandleOnLeave; - - base.OnMouseLeave (mouseEvent); - - return mouseEvent.Handled; - } - } - - [Fact] - public void RaiseMouseEnterLeaveEvents_MouseEntersView_CallsOnMouseEnter () - { - // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; - var view = new TestView (); - Application.Top.Add (view); - var mousePosition = new Point (1, 1); - List currentViewsUnderMouse = new () { view }; - var mouseEvent = new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }; - - Application._cachedViewsUnderMouse.Clear (); - - try - { - // Act - Application.RaiseMouseEnterLeaveEvents (mousePosition, currentViewsUnderMouse, mouseEvent); - - // Assert - Assert.Equal (1, view.OnMouseEnterCalled); - } - finally - { - // Cleanup - Application.Top?.Dispose (); - Application.ResetState (); - } - } - - [Fact] - public void RaiseMouseEnterLeaveEvents_MouseLeavesView_CallsOnMouseLeave () - { - // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; - var view = new TestView (); - Application.Top.Add (view); - var mousePosition = new Point (0, 0); - List currentViewsUnderMouse = new (); - var mouseEvent = new MouseEvent (); - - Application._cachedViewsUnderMouse.Clear (); - Application._cachedViewsUnderMouse.Add (view); - - try - { - // Act - Application.RaiseMouseEnterLeaveEvents (mousePosition, currentViewsUnderMouse, mouseEvent); - - // Assert - Assert.Equal (0, view.OnMouseEnterCalled); - Assert.Equal (1, view.OnMouseLeaveCalled); - } - finally - { - // Cleanup - Application.Top?.Dispose (); - Application.ResetState (); - } - } - - [Fact] - public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenAdjacentViews_CallsOnMouseEnterAndLeave () - { - // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; - var view1 = new TestView (); // at 1,1 to 2,2 - var view2 = new TestView () // at 2,2 to 3,3 - { - X = 2, - Y = 2, - }; - Application.Top.Add (view1); - Application.Top.Add (view2); - - - - Application._cachedViewsUnderMouse.Clear (); - - try - { - // Act - var mousePosition = new Point (0, 0); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (0, view1.OnMouseEnterCalled); - Assert.Equal (0, view1.OnMouseLeaveCalled); - Assert.Equal (0, view2.OnMouseEnterCalled); - Assert.Equal (0, view2.OnMouseLeaveCalled); - - // Act - mousePosition = new Point (1, 1); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (0, view1.OnMouseLeaveCalled); - Assert.Equal (0, view2.OnMouseEnterCalled); - Assert.Equal (0, view2.OnMouseLeaveCalled); - - // Act - mousePosition = new Point (2, 2); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (1, view1.OnMouseLeaveCalled); - Assert.Equal (1, view2.OnMouseEnterCalled); - Assert.Equal (0, view2.OnMouseLeaveCalled); - - - // Act - mousePosition = new Point (3, 3); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (1, view1.OnMouseLeaveCalled); - Assert.Equal (1, view2.OnMouseEnterCalled); - Assert.Equal (1, view2.OnMouseLeaveCalled); - - // Act - mousePosition = new Point (0, 0); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (1, view1.OnMouseLeaveCalled); - Assert.Equal (1, view2.OnMouseEnterCalled); - Assert.Equal (1, view2.OnMouseLeaveCalled); - } - finally - { - // Cleanup - Application.Top?.Dispose (); - Application.ResetState (); - } - } - - [Fact] - public void RaiseMouseEnterLeaveEvents_NoViewsUnderMouse_DoesNotCallOnMouseEnterOrLeave () - { - // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; - var view = new TestView (); - Application.Top.Add (view); - var mousePosition = new Point (0, 0); - List currentViewsUnderMouse = new (); - var mouseEvent = new MouseEvent (); - - Application._cachedViewsUnderMouse.Clear (); - - try - { - // Act - Application.RaiseMouseEnterLeaveEvents (mousePosition, currentViewsUnderMouse, mouseEvent); - - // Assert - Assert.Equal (0, view.OnMouseEnterCalled); - Assert.Equal (0, view.OnMouseLeaveCalled); - } - finally - { - // Cleanup - Application.Top?.Dispose (); - Application.ResetState (); - } - } - - - [Fact] - public void RaiseMouseEnterLeaveEvents_MouseMovesBetweenOverlappingViews_CallsOnMouseEnterAndLeave () - { - // Arrange - Application.Top = new () { Frame = new (0, 0, 10, 10) }; - var view1 = new TestView () - { - Width = 2 - }; // at 1,1 to 3,2 - var view2 = new TestView () // at 2,2 to 4,3 - { - Width = 2, - X = 2, - Y = 2, - }; - Application.Top.Add (view1); - Application.Top.Add (view2); - - Application._cachedViewsUnderMouse.Clear (); - - try - { - // Act - var mousePosition = new Point (0, 0); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (0, view1.OnMouseEnterCalled); - Assert.Equal (0, view1.OnMouseLeaveCalled); - Assert.Equal (0, view2.OnMouseEnterCalled); - Assert.Equal (0, view2.OnMouseLeaveCalled); - - // Act - mousePosition = new Point (1, 1); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (0, view1.OnMouseLeaveCalled); - Assert.Equal (0, view2.OnMouseEnterCalled); - Assert.Equal (0, view2.OnMouseLeaveCalled); - - // Act - mousePosition = new Point (2, 2); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (1, view1.OnMouseLeaveCalled); - Assert.Equal (1, view2.OnMouseEnterCalled); - Assert.Equal (0, view2.OnMouseLeaveCalled); - - - // Act - mousePosition = new Point (3, 3); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (1, view1.OnMouseLeaveCalled); - Assert.Equal (1, view2.OnMouseEnterCalled); - Assert.Equal (1, view2.OnMouseLeaveCalled); - - // Act - mousePosition = new Point (0, 0); - Application.RaiseMouseEnterLeaveEvents (mousePosition, View.GetViewsUnderMouse (mousePosition), new MouseEvent () - { - Position = mousePosition, - ScreenPosition = mousePosition - }); - - // Assert - Assert.Equal (1, view1.OnMouseEnterCalled); - Assert.Equal (1, view1.OnMouseLeaveCalled); - Assert.Equal (1, view2.OnMouseEnterCalled); - Assert.Equal (1, view2.OnMouseLeaveCalled); - } - finally - { - // Cleanup - Application.Top?.Dispose (); - Application.ResetState (); - } - } -} diff --git a/UnitTests/Input/ResponderTests.cs b/UnitTests/Input/ResponderTests.cs index 85c2d8764..cd02fa0ed 100644 --- a/UnitTests/Input/ResponderTests.cs +++ b/UnitTests/Input/ResponderTests.cs @@ -235,8 +235,6 @@ public class ResponderTests Assert.False (r.OnKeyDown (new Key { KeyCode = KeyCode.Null })); Assert.False (r.OnKeyUp (new Key { KeyCode = KeyCode.Null })); Assert.False (r.NewMouseEvent (new MouseEvent { Flags = MouseFlags.AllEvents })); - Assert.False (r.NewMouseEnterEvent (new MouseEvent { Flags = MouseFlags.AllEvents })); - Assert.False (r.NewMouseLeaveEvent (new MouseEvent { Flags = MouseFlags.AllEvents })); var v = new View (); //Assert.False (r.OnEnter (v)); diff --git a/UnitTests/View/Mouse/GetViewsUnderMouseTests.cs b/UnitTests/View/Mouse/GetViewsUnderMouseTests.cs index f101c118c..7a58779b2 100644 --- a/UnitTests/View/Mouse/GetViewsUnderMouseTests.cs +++ b/UnitTests/View/Mouse/GetViewsUnderMouseTests.cs @@ -2,6 +2,7 @@ namespace Terminal.Gui.ViewMouseTests; +[Trait ("Category", "Input")] public class GetViewsUnderMouseTests { [Theory] @@ -57,21 +58,30 @@ public class GetViewsUnderMouseTests [InlineData (1, 1, 1, 1, 1, 8, 8, typeof (Padding))] [InlineData (1, 1, 1, 1, 1, 9, 9, typeof (Border))] [InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))] - public void GetViewsUnderMouse_Top_Adornments_Returns_Correct_View (int frameX, int frameY, int marginThickness, int borderThickness, int paddingThickness, int testX, int testY, Type? expectedViewType) + public void GetViewsUnderMouse_Top_Adornments_Returns_Correct_View ( + int frameX, + int frameY, + int marginThickness, + int borderThickness, + int paddingThickness, + int testX, + int testY, + Type? expectedViewType + ) { // Arrange Application.Top = new () { - Frame = new Rectangle (frameX, frameY, 10, 10), + Frame = new (frameX, frameY, 10, 10) }; - Application.Top.Margin.Thickness = new Thickness (marginThickness); - Application.Top.Border.Thickness = new Thickness (borderThickness); - Application.Top.Padding.Thickness = new Thickness (paddingThickness); + Application.Top.Margin.Thickness = new (marginThickness); + Application.Top.Border.Thickness = new (borderThickness); + Application.Top.Padding.Thickness = new (paddingThickness); var location = new Point (testX, testY); // Act - var viewsUnderMouse = View.GetViewsUnderMouse (location); + List viewsUnderMouse = View.GetViewsUnderMouse (location); // Assert if (expectedViewType == null) @@ -82,8 +92,9 @@ public class GetViewsUnderMouseTests { Assert.Contains (viewsUnderMouse, v => v?.GetType () == expectedViewType); } + Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -95,18 +106,18 @@ public class GetViewsUnderMouseTests // Arrange Application.Top = new () { - Frame = new Rectangle (0, 0, 10, 10) + Frame = new (0, 0, 10, 10) }; var location = new Point (testX, testY); // Act - var viewsUnderMouse = View.GetViewsUnderMouse (location); + List viewsUnderMouse = View.GetViewsUnderMouse (location); // Assert Assert.Contains (viewsUnderMouse, v => v == Application.Top); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -118,13 +129,13 @@ public class GetViewsUnderMouseTests // Arrange var view = new View { - Frame = new Rectangle (0, 0, 10, 10) + Frame = new (0, 0, 10, 10) }; var location = new Point (testX, testY); // Act - var viewsUnderMouse = View.GetViewsUnderMouse (location); + List viewsUnderMouse = View.GetViewsUnderMouse (location); // Assert Assert.Empty (viewsUnderMouse); @@ -139,14 +150,14 @@ public class GetViewsUnderMouseTests // Arrange var view = new View { - Frame = new Rectangle (0, 0, 10, 10), + Frame = new (0, 0, 10, 10), Visible = false }; var location = new Point (testX, testY); // Act - var viewsUnderMouse = View.GetViewsUnderMouse (location); + List viewsUnderMouse = View.GetViewsUnderMouse (location); // Assert Assert.Empty (viewsUnderMouse); @@ -165,12 +176,12 @@ public class GetViewsUnderMouseTests // Arrange Application.Top = new () { - Frame = new Rectangle (0, 0, 10, 10) + Frame = new (0, 0, 10, 10) }; var subView = new View { - Frame = new Rectangle (1, 1, 8, 8) + Frame = new (1, 1, 8, 8) }; Application.Top.Add (subView); @@ -178,7 +189,7 @@ public class GetViewsUnderMouseTests var location = new Point (testX, testY); // Act - var viewsUnderMouse = View.GetViewsUnderMouse (location); + List viewsUnderMouse = View.GetViewsUnderMouse (location); // Assert if (expected) @@ -189,39 +200,36 @@ public class GetViewsUnderMouseTests { Assert.DoesNotContain (viewsUnderMouse, v => v == subView); } + Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } - [Theory] + [Theory] [InlineData (0, 0, 0, 0, 0, -1, -1, null)] [InlineData (0, 0, 0, 0, 0, 0, 0, typeof (View))] [InlineData (0, 0, 0, 0, 0, 1, 1, typeof (View))] [InlineData (0, 0, 0, 0, 0, 4, 4, typeof (View))] [InlineData (0, 0, 0, 0, 0, 9, 9, typeof (View))] [InlineData (0, 0, 0, 0, 0, 10, 10, null)] - [InlineData (1, 1, 0, 0, 0, -1, -1, null)] [InlineData (1, 1, 0, 0, 0, 0, 0, null)] [InlineData (1, 1, 0, 0, 0, 1, 1, typeof (View))] [InlineData (1, 1, 0, 0, 0, 4, 4, typeof (View))] [InlineData (1, 1, 0, 0, 0, 9, 9, typeof (View))] [InlineData (1, 1, 0, 0, 0, 10, 10, typeof (View))] - [InlineData (0, 0, 1, 0, 0, -1, -1, null)] [InlineData (0, 0, 1, 0, 0, 0, 0, typeof (Margin))] [InlineData (0, 0, 1, 0, 0, 1, 1, typeof (View))] [InlineData (0, 0, 1, 0, 0, 4, 4, typeof (View))] [InlineData (0, 0, 1, 0, 0, 9, 9, typeof (Margin))] [InlineData (0, 0, 1, 0, 0, 10, 10, null)] - [InlineData (0, 0, 1, 1, 0, -1, -1, null)] [InlineData (0, 0, 1, 1, 0, 0, 0, typeof (Margin))] [InlineData (0, 0, 1, 1, 0, 1, 1, typeof (Border))] [InlineData (0, 0, 1, 1, 0, 4, 4, typeof (View))] [InlineData (0, 0, 1, 1, 0, 9, 9, typeof (Margin))] [InlineData (0, 0, 1, 1, 0, 10, 10, null)] - [InlineData (0, 0, 1, 1, 1, -1, -1, null)] [InlineData (0, 0, 1, 1, 1, 0, 0, typeof (Margin))] [InlineData (0, 0, 1, 1, 1, 1, 1, typeof (Border))] @@ -229,7 +237,6 @@ public class GetViewsUnderMouseTests [InlineData (0, 0, 1, 1, 1, 4, 4, typeof (View))] [InlineData (0, 0, 1, 1, 1, 9, 9, typeof (Margin))] [InlineData (0, 0, 1, 1, 1, 10, 10, null)] - [InlineData (1, 1, 1, 0, 0, -1, -1, null)] [InlineData (1, 1, 1, 0, 0, 0, 0, null)] [InlineData (1, 1, 1, 0, 0, 1, 1, typeof (Margin))] @@ -251,18 +258,28 @@ public class GetViewsUnderMouseTests [InlineData (1, 1, 1, 1, 1, 8, 8, typeof (Padding))] [InlineData (1, 1, 1, 1, 1, 9, 9, typeof (Border))] [InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))] - public void Contains (int frameX, int frameY, int marginThickness, int borderThickness, int paddingThickness, int testX, int testY, Type? expectedAdornmentType) + public void Contains ( + int frameX, + int frameY, + int marginThickness, + int borderThickness, + int paddingThickness, + int testX, + int testY, + Type? expectedAdornmentType + ) { - var view = new View () + var view = new View { X = frameX, Y = frameY, - Width = 10, Height = 10, + Width = 10, Height = 10 }; - view.Margin.Thickness = new Thickness (marginThickness); - view.Border.Thickness = new Thickness (borderThickness); - view.Padding.Thickness = new Thickness (paddingThickness); + view.Margin.Thickness = new (marginThickness); + view.Border.Thickness = new (borderThickness); + view.Padding.Thickness = new (paddingThickness); Type? containedType = null; + if (view.Contains (new (testX, testY))) { containedType = view.GetType (); @@ -282,8 +299,8 @@ public class GetViewsUnderMouseTests { containedType = view.Padding.GetType (); } - Assert.Equal (expectedAdornmentType, containedType); + Assert.Equal (expectedAdornmentType, containedType); } // Test that GetViewsUnderMouse returns the correct view if the start view has no subviews @@ -295,12 +312,12 @@ public class GetViewsUnderMouseTests { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - Assert.Same (Application.Top, View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault()); + Assert.Same (Application.Top, View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ()); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } // Test that GetViewsUnderMouse returns null if the start view has no subviews and coords are outside the view @@ -313,12 +330,12 @@ public class GetViewsUnderMouseTests Application.Top = new () { X = 1, Y = 2, - Width = 10, Height = 10, + Width = 10, Height = 10 }; - Assert.Null (View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault()); + Assert.Null (View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ()); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -331,12 +348,12 @@ public class GetViewsUnderMouseTests { X = 1, Y = 2, Width = 10, Height = 10, - Visible = false, + Visible = false }; - Assert.Null (View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault()); + Assert.Null (View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ()); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } // Test that GetViewsUnderMouse returns the correct view if the start view has subviews @@ -346,28 +363,27 @@ public class GetViewsUnderMouseTests [InlineData (9, 9, false)] [InlineData (10, 10, false)] [InlineData (6, 7, false)] - [InlineData (1, 2, true)] [InlineData (5, 6, true)] public void Returns_Correct_If_SubViews (int testX, int testY, bool expectedSubViewFound) { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - var subview = new View () + var subview = new View { X = 1, Y = 2, - Width = 5, Height = 5, + Width = 5, Height = 5 }; Application.Top.Add (subview); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -382,10 +398,10 @@ public class GetViewsUnderMouseTests { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - var subview = new View () + var subview = new View { X = 1, Y = 2, Width = 5, Height = 5, @@ -393,14 +409,13 @@ public class GetViewsUnderMouseTests }; Application.Top.Add (subview); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } - [Theory] [InlineData (0, 0, false)] [InlineData (1, 1, false)] @@ -417,20 +432,20 @@ public class GetViewsUnderMouseTests Visible = false }; - var subview = new View () + var subview = new View { X = 1, Y = 2, - Width = 5, Height = 5, + Width = 5, Height = 5 }; Application.Top.Add (subview); subview.Visible = true; Assert.True (subview.Visible); Assert.False (Application.Top.Visible); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } // Test that GetViewsUnderMouse works if the start view has positive Adornments @@ -441,7 +456,6 @@ public class GetViewsUnderMouseTests [InlineData (10, 10, false)] [InlineData (7, 8, false)] [InlineData (1, 2, false)] - [InlineData (2, 3, true)] [InlineData (5, 6, true)] [InlineData (6, 7, true)] @@ -449,22 +463,22 @@ public class GetViewsUnderMouseTests { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - Application.Top.Margin.Thickness = new Thickness (1); + Application.Top.Margin.Thickness = new (1); - var subview = new View () + var subview = new View { X = 1, Y = 2, - Width = 5, Height = 5, + Width = 5, Height = 5 }; Application.Top.Add (subview); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } // Test that GetViewsUnderMouse works if the start view has offset Viewport location @@ -472,7 +486,6 @@ public class GetViewsUnderMouseTests [InlineData (1, 0, 0, true)] [InlineData (1, 1, 1, true)] [InlineData (1, 2, 2, false)] - [InlineData (-1, 3, 3, true)] [InlineData (-1, 2, 2, true)] [InlineData (-1, 1, 1, false)] @@ -486,18 +499,18 @@ public class GetViewsUnderMouseTests }; Application.Top.Viewport = new (offset, offset, 10, 10); - var subview = new View () + var subview = new View { X = 1, Y = 1, - Width = 2, Height = 2, + Width = 2, Height = 2 }; Application.Top.Add (subview); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -514,59 +527,55 @@ public class GetViewsUnderMouseTests { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - Application.Top.Padding.Thickness = new Thickness (1); + Application.Top.Padding.Thickness = new (1); - var subview = new View () + var subview = new View { X = Pos.AnchorEnd (1), Y = Pos.AnchorEnd (1), - Width = 1, Height = 1, + Width = 1, Height = 1 }; Application.Top.Padding.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } - [Theory] [InlineData (0, 0, typeof (Margin))] [InlineData (9, 9, typeof (Margin))] - [InlineData (1, 1, typeof (Border))] [InlineData (8, 8, typeof (Border))] - [InlineData (2, 2, typeof (Padding))] [InlineData (7, 7, typeof (Padding))] - [InlineData (5, 5, typeof (Toplevel))] public void Returns_Adornment_If_Start_Has_Adornments (int testX, int testY, Type expectedAdornmentType) { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - Application.Top.Margin.Thickness = new Thickness (1); - Application.Top.Border.Thickness = new Thickness (1); - Application.Top.Padding.Thickness = new Thickness (1); + Application.Top.Margin.Thickness = new (1); + Application.Top.Border.Thickness = new (1); + Application.Top.Padding.Thickness = new (1); - var subview = new View () + var subview = new View { X = 1, Y = 1, - Width = 1, Height = 1, + Width = 1, Height = 1 }; Application.Top.Add (subview); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedAdornmentType, found!.GetType ()); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } // Test that GetViewsUnderMouse works if the subview has positive Adornments @@ -579,28 +588,27 @@ public class GetViewsUnderMouseTests [InlineData (6, 7, false)] [InlineData (1, 2, false)] [InlineData (5, 6, false)] - [InlineData (2, 3, true)] public void Returns_Correct_If_SubView_Has_Adornments (int testX, int testY, bool expectedSubViewFound) { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; - var subview = new View () + var subview = new View { X = 1, Y = 2, - Width = 5, Height = 5, + Width = 5, Height = 5 }; - subview.Margin.Thickness = new Thickness (1); + subview.Margin.Thickness = new (1); Application.Top.Add (subview); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == subview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -618,36 +626,36 @@ public class GetViewsUnderMouseTests { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; // A subview with + Padding - var subview = new View () + var subview = new View { X = 1, Y = 1, - Width = 5, Height = 5, + Width = 5, Height = 5 }; subview.Padding.Thickness = new (1); // This subview will be at the bottom-right-corner of subview // So screen-relative location will be X + Width - 1 = 5 - var paddingSubview = new View () + var paddingSubview = new View { X = Pos.AnchorEnd (1), Y = Pos.AnchorEnd (1), Width = 1, - Height = 1, + Height = 1 }; subview.Padding.Add (paddingSubview); Application.Top.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); - var found = View.GetViewsUnderMouse(new Point(testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == paddingSubview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } [Theory] @@ -665,14 +673,14 @@ public class GetViewsUnderMouseTests { Application.Top = new () { - Width = 10, Height = 10, + Width = 10, Height = 10 }; // A subview with + Padding - var subview = new View () + var subview = new View { X = 1, Y = 1, - Width = 5, Height = 5, + Width = 5, Height = 5 }; subview.Padding.Thickness = new (1); @@ -682,23 +690,23 @@ public class GetViewsUnderMouseTests // This subview will be at the bottom-right-corner of subview // So screen-relative location will be X + Width - 1 = 5 - var paddingSubview = new View () + var paddingSubview = new View { X = Pos.AnchorEnd (1), Y = Pos.AnchorEnd (1), Width = 1, - Height = 1, + Height = 1 }; subview.Padding.Add (paddingSubview); Application.Top.Add (subview); Application.Top.BeginInit (); Application.Top.EndInit (); - var found = View.GetViewsUnderMouse(new (testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, found == paddingSubview); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); } // Test that GetViewsUnderMouse works with nested subviews @@ -706,7 +714,6 @@ public class GetViewsUnderMouseTests [InlineData (0, 0, -1)] [InlineData (9, 9, -1)] [InlineData (10, 10, -1)] - [InlineData (1, 1, 0)] [InlineData (1, 2, 0)] [InlineData (2, 2, 1)] @@ -719,14 +726,15 @@ public class GetViewsUnderMouseTests Width = 10, Height = 10 }; - int numSubViews = 3; - List subviews = new List (); - for (int i = 0; i < numSubViews; i++) + var numSubViews = 3; + List subviews = new (); + + for (var i = 0; i < numSubViews; i++) { - var subview = new View () + var subview = new View { X = 1, Y = 1, - Width = 5, Height = 5, + Width = 5, Height = 5 }; subviews.Add (subview); @@ -738,9 +746,60 @@ public class GetViewsUnderMouseTests Application.Top.Add (subviews [0]); - var found = View.GetViewsUnderMouse(new (testX, testY)).LastOrDefault(); + View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault (); Assert.Equal (expectedSubViewFound, subviews.IndexOf (found!)); Application.Top.Dispose (); - Application.ResetState (ignoreDisposed: true); + Application.ResetState (true); + } + + [Theory] + [InlineData (0, 0, new [] { "top" })] + [InlineData (9, 9, new [] { "top" })] + [InlineData (10, 10, new string [] { })] + [InlineData (1, 1, new [] { "top", "view" })] + [InlineData (1, 2, new [] { "top", "view" })] + [InlineData (2, 1, new [] { "top", "view" })] + [InlineData (2, 2, new [] { "top", "view", "subView" })] + [InlineData (3, 3, new [] { "top" })] // clipped + [InlineData (2, 3, new [] { "top" })] // clipped + public void GetViewsUnderMouse_Tiled_Subviews (int mouseX, int mouseY, string [] viewIdStrings) + { + // Arrange + Application.Top = new () + { + Frame = new (0, 0, 10, 10), + Id = "top" + }; + + var view = new View + { + Id = "view", + X = 1, + Y = 1, + Width = 2, + Height = 2, + Arrangement = ViewArrangement.Overlapped + }; // at 1,1 to 3,2 (screen) + + var subView = new View + { + Id = "subView", + X = 1, + Y = 1, + Width = 2, + Height = 2, + Arrangement = ViewArrangement.Overlapped + }; // at 2,2 to 4,3 (screen) + view.Add (subView); + Application.Top.Add (view); + + List found = View.GetViewsUnderMouse (new (mouseX, mouseY)); + + string [] foundIds = found.Select (v => v.Id).ToArray (); + + Assert.Equal (viewIdStrings, foundIds); + + Application.Top.Dispose (); + Application.ResetState (true); } } diff --git a/UnitTests/View/Mouse/MouseEnterLeaveTests.cs b/UnitTests/View/Mouse/MouseEnterLeaveTests.cs index 1aed128d9..ea28c291d 100644 --- a/UnitTests/View/Mouse/MouseEnterLeaveTests.cs +++ b/UnitTests/View/Mouse/MouseEnterLeaveTests.cs @@ -1,3 +1,5 @@ +using System.ComponentModel; + namespace Terminal.Gui.ViewMouseTests; [Trait ("Category", "Input")] @@ -11,29 +13,26 @@ public class MouseEnterLeaveTests MouseLeave += OnMouseLeaveHandler; } - public bool HandleOnEnter { get; init; } - public bool HandleOnLeave { get; } + public bool CancelOnEnter { get; init; } - public bool HandleEnterEvent { get; init; } - public bool HandleLeaveEvent { get; } + public bool CancelEnterEvent { get; init; } public bool OnMouseEnterCalled { get; private set; } public bool OnMouseLeaveCalled { get; private set; } - protected internal override bool? OnMouseEnter (MouseEvent mouseEvent) + protected override bool OnMouseEnter (CancelEventArgs eventArgs) { OnMouseEnterCalled = true; - mouseEvent.Handled = HandleOnEnter; + eventArgs.Cancel = CancelOnEnter; - base.OnMouseEnter (mouseEvent); + base.OnMouseEnter (eventArgs); - return mouseEvent.Handled; + return eventArgs.Cancel; } protected internal override bool OnMouseLeave (MouseEvent mouseEvent) { OnMouseLeaveCalled = true; - mouseEvent.Handled = HandleOnLeave; base.OnMouseLeave (mouseEvent); @@ -43,25 +42,17 @@ public class MouseEnterLeaveTests public bool MouseEnterRaised { get; private set; } public bool MouseLeaveRaised { get; private set; } - private void OnMouseEnterHandler (object s, MouseEventEventArgs e) + private void OnMouseEnterHandler (object s, CancelEventArgs e) { MouseEnterRaised = true; - if (HandleEnterEvent) + if (CancelEnterEvent) { - e.Handled = true; + e.Cancel = true; } } - private void OnMouseLeaveHandler (object s, MouseEventEventArgs e) - { - MouseLeaveRaised = true; - - if (HandleLeaveEvent) - { - e.Handled = true; - } - } + private void OnMouseLeaveHandler (object s, MouseEventEventArgs e) { MouseLeaveRaised = true; } } [Fact] @@ -76,20 +67,22 @@ public class MouseEnterLeaveTests var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); + // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert Assert.True (view.OnMouseEnterCalled); - Assert.False (handled); - Assert.False (mouseEvent.Handled); + Assert.False (cancelled); + Assert.False (eventArgs.Cancel); // Cleanup view.Dispose (); } [Fact] - public void NewMouseEnterEvent_ViewIsDisabled_DoesNotCallOnMouseEnter () + public void NewMouseEnterEvent_ViewIsDisabled_CallsOnMouseEnter () { // Arrange var view = new TestView @@ -98,15 +91,15 @@ public class MouseEnterLeaveTests Visible = true }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert - Assert.False (view.OnMouseEnterCalled); - Assert.False (handled); - Assert.False (mouseEvent.Handled); + Assert.True (view.OnMouseEnterCalled); + Assert.False (cancelled); + Assert.False (eventArgs.Cancel); // Cleanup view.Dispose (); @@ -122,15 +115,15 @@ public class MouseEnterLeaveTests Visible = false }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert Assert.False (view.OnMouseEnterCalled); - Assert.False (handled); - Assert.False (mouseEvent.Handled); + Assert.Null (cancelled); + Assert.False (eventArgs.Cancel); // Cleanup view.Dispose (); @@ -148,11 +141,11 @@ public class MouseEnterLeaveTests var mouseEvent = new MouseEvent (); // Act - bool? handled = view.NewMouseLeaveEvent (mouseEvent); + bool? cancelled = view.NewMouseLeaveEvent (mouseEvent); // Assert Assert.True (view.OnMouseLeaveCalled); - Assert.False (handled); + Assert.False (cancelled); Assert.False (mouseEvent.Handled); // Cleanup @@ -172,11 +165,11 @@ public class MouseEnterLeaveTests var mouseEvent = new MouseEvent (); // Act - bool? handled = view.NewMouseLeaveEvent (mouseEvent); + bool? cancelled = view.NewMouseLeaveEvent (mouseEvent); // Assert Assert.True (view.OnMouseLeaveCalled); - Assert.False (handled); + Assert.False (cancelled); Assert.False (mouseEvent.Handled); // Cleanup @@ -195,22 +188,22 @@ public class MouseEnterLeaveTests Visible = true }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert Assert.True (view.MouseEnterRaised); - Assert.False (handled); - Assert.False (mouseEvent.Handled); + Assert.False (cancelled); + Assert.False (eventArgs.Cancel); // Cleanup view.Dispose (); } [Fact] - public void NewMouseEnterEvent_ViewIsDisabled_DoesNotRaiseMouseEnter () + public void NewMouseEnterEvent_ViewIsDisabled_RaisesMouseEnter () { // Arrange var view = new TestView @@ -219,15 +212,15 @@ public class MouseEnterLeaveTests Visible = true }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert - Assert.False (view.MouseEnterRaised); - Assert.False (handled); - Assert.False (mouseEvent.Handled); + Assert.True (view.MouseEnterRaised); + Assert.False (cancelled); + Assert.False (eventArgs.Cancel); // Cleanup view.Dispose (); @@ -243,15 +236,15 @@ public class MouseEnterLeaveTests Visible = false }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert Assert.False (view.MouseEnterRaised); - Assert.False (handled); - Assert.False (mouseEvent.Handled); + Assert.Null (cancelled); + Assert.False (eventArgs.Cancel); // Cleanup view.Dispose (); @@ -263,18 +256,18 @@ public class MouseEnterLeaveTests // Arrange var view = new TestView { - Enabled = true, + Enabled = true, Visible = true }; var mouseEvent = new MouseEvent (); // Act - bool? handled = view.NewMouseLeaveEvent (mouseEvent); + bool? cancelled = view.NewMouseLeaveEvent (mouseEvent); // Assert Assert.True (view.MouseLeaveRaised); - Assert.False (handled); + Assert.False (cancelled); Assert.False (mouseEvent.Handled); // Cleanup @@ -282,7 +275,7 @@ public class MouseEnterLeaveTests } [Fact] - public void NewMouseLeaveEvent_ViewIsNotVisible_DoesNotRaiseMouseLeave () + public void NewMouseLeaveEvent_ViewIsNotVisible_RaisesMouseLeave () { // Arrange var view = new TestView @@ -294,11 +287,11 @@ public class MouseEnterLeaveTests var mouseEvent = new MouseEvent (); // Act - bool? handled = view.NewMouseLeaveEvent (mouseEvent); + bool? cancelled = view.NewMouseLeaveEvent (mouseEvent); // Assert Assert.True (view.MouseLeaveRaised); - Assert.False (handled); + Assert.False (cancelled); Assert.False (mouseEvent.Handled); // Cleanup @@ -314,18 +307,18 @@ public class MouseEnterLeaveTests { Enabled = true, Visible = true, - HandleOnEnter = true + CancelOnEnter = true }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert Assert.True (view.OnMouseEnterCalled); - Assert.True (handled); - Assert.True (mouseEvent.Handled); + Assert.True (cancelled); + Assert.True (eventArgs.Cancel); Assert.False (view.MouseEnterRaised); @@ -341,18 +334,18 @@ public class MouseEnterLeaveTests { Enabled = true, Visible = true, - HandleEnterEvent = true + CancelEnterEvent = true }; - var mouseEvent = new MouseEvent (); + var eventArgs = new CancelEventArgs (); // Act - bool? handled = view.NewMouseEnterEvent (mouseEvent); + bool? cancelled = view.NewMouseEnterEvent (eventArgs); // Assert Assert.True (view.OnMouseEnterCalled); - Assert.True (handled); - Assert.True (mouseEvent.Handled); + Assert.True (cancelled); + Assert.True (eventArgs.Cancel); Assert.True (view.MouseEnterRaised); diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index e4f0616ed..cb719618a 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -874,8 +874,6 @@ At 0,0 //Assert.False (r.OnKeyDown (new KeyEventArgs () { Key = Key.Unknown })); Assert.False (r.OnKeyUp (new() { KeyCode = KeyCode.Null })); Assert.False (r.NewMouseEvent (new() { Flags = MouseFlags.AllEvents })); - Assert.False (r.NewMouseEnterEvent (new() { Flags = MouseFlags.AllEvents })); - Assert.False (r.NewMouseLeaveEvent (new() { Flags = MouseFlags.AllEvents })); r.Dispose ();