Sfoglia il codice sorgente

Merge pull request #3277 from tig/v2_3276_adornment_bounds

Fixes #3276. `Adornment.Bounds/FrameToScreen`
Tig 1 anno fa
parent
commit
c21107efb9

+ 9 - 6
Terminal.Gui/Application.cs

@@ -1374,7 +1374,8 @@ public static partial class Application
             return;
             return;
         }
         }
 
 
-        var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y, out int screenX, out int screenY);
+        // TODO: In PR #3273, FindDeepestView will return adornments. Update logic below to fix adornment mouse handling
+        var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y);
 
 
         if (view is { WantContinuousButtonPressed: true })
         if (view is { WantContinuousButtonPressed: true })
         {
         {
@@ -1437,7 +1438,7 @@ public static partial class Application
             && a.MouseEvent.Flags != 0)
             && a.MouseEvent.Flags != 0)
         {
         {
             View? top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y);
             View? top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y);
-            view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out screenX, out screenY);
+            view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y);
 
 
             if (view is { } && view != OverlappedTop && top != Current)
             if (view is { } && view != OverlappedTop && top != Current)
             {
             {
@@ -1450,6 +1451,8 @@ public static partial class Application
             return;
             return;
         }
         }
 
 
+        var screen = view.FrameToScreen ();
+
         // Work inside-out (Padding, Border, Margin)
         // Work inside-out (Padding, Border, Margin)
         // TODO: Debate whether inside-out or outside-in is the right strategy
         // TODO: Debate whether inside-out or outside-in is the right strategy
         if (AdornmentHandledMouseEvent (view.Padding, a))
         if (AdornmentHandledMouseEvent (view.Padding, a))
@@ -1469,11 +1472,11 @@ public static partial class Application
 
 
             var me = new MouseEvent
             var me = new MouseEvent
             {
             {
-                X = screenX,
-                Y = screenY,
+                X = a.MouseEvent.X - screen.X,
+                Y = a.MouseEvent.Y - screen.Y,
                 Flags = a.MouseEvent.Flags,
                 Flags = a.MouseEvent.Flags,
-                OfX = screenX,
-                OfY = screenY,
+                OfX = a.MouseEvent.X - screen.X,
+                OfY = a.MouseEvent.Y - screen.Y,
                 View = view
                 View = view
             };
             };
 
 

+ 9 - 0
Terminal.Gui/Input/Responder.cs

@@ -57,6 +57,9 @@ public class Responder : IDisposable
     public event EventHandler Disposing;
     public event EventHandler Disposing;
 
 
     /// <summary>Method invoked when a mouse event is generated</summary>
     /// <summary>Method invoked when a mouse event is generated</summary>
+    /// <remarks>
+    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    /// </remarks>
     /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
     /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
     /// <param name="mouseEvent">Contains the details about the mouse event.</param>
     /// <param name="mouseEvent">Contains the details about the mouse event.</param>
     public virtual bool MouseEvent (MouseEvent mouseEvent) { return false; }
     public virtual bool MouseEvent (MouseEvent mouseEvent) { return false; }
@@ -81,6 +84,9 @@ public class Responder : IDisposable
     ///     Called when the mouse first enters the view; the view will now receives mouse events until the mouse leaves
     ///     Called when the mouse first enters the view; the view will now receives mouse events until the mouse leaves
     ///     the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/> will be called.
     ///     the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/> will be called.
     /// </summary>
     /// </summary>
+    /// <remarks>
+    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <param name="mouseEvent"></param>
     /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
     /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
     public virtual bool OnMouseEnter (MouseEvent mouseEvent) { return false; }
     public virtual bool OnMouseEnter (MouseEvent mouseEvent) { return false; }
@@ -89,6 +95,9 @@ public class Responder : IDisposable
     ///     Called when the mouse has moved outside of the view; the view will no longer receive mouse events (until the
     ///     Called when the mouse has moved outside of the view; the view will no longer receive mouse events (until the
     ///     mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
     ///     mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
     /// </summary>
     /// </summary>
+    /// <remarks>
+    /// The coordinates are relative to <see cref="View.Bounds"/>.
+    /// </remarks>
     /// <param name="mouseEvent"></param>
     /// <param name="mouseEvent"></param>
     /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
     /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
     public virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; }
     public virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; }

+ 8 - 6
Terminal.Gui/View/Adornment/Adornment.cs

@@ -33,7 +33,7 @@ public class Adornment : View
     /// <summary>Gets the rectangle that describes the inner area of the Adornment. The Location is always (0,0).</summary>
     /// <summary>Gets the rectangle that describes the inner area of the Adornment. The Location is always (0,0).</summary>
     public override Rectangle Bounds
     public override Rectangle Bounds
     {
     {
-        get => Thickness?.GetInside (new (Point.Empty, Frame.Size)) ?? new Rectangle (Point.Empty, Frame.Size);
+        get => new Rectangle (Point.Empty, Thickness?.GetInside (new (Point.Empty, Frame.Size)).Size ?? Frame.Size);
         // QUESTION: So why even have a setter then?
         // QUESTION: So why even have a setter then?
         set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
         set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
     }
     }
@@ -100,17 +100,19 @@ public class Adornment : View
     /// <inheritdoc/>
     /// <inheritdoc/>
     public override Rectangle FrameToScreen ()
     public override Rectangle FrameToScreen ()
     {
     {
+        if (Parent is null)
+        {
+            return Frame;
+        }
+
         // Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
         // Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
         // To get the screen-relative coordinates of a Adornment, we need to know who
         // To get the screen-relative coordinates of a Adornment, we need to know who
         // the Parent is
         // the Parent is
-        Rectangle ret = Parent?.Frame ?? Frame;
-        ret.Size = Frame.Size;
-
-        ret.Location = Parent?.FrameToScreen ().Location ?? ret.Location;
+        Rectangle parent = Parent.FrameToScreen ();
 
 
         // We now have coordinates relative to our View. If our View's SuperView has
         // We now have coordinates relative to our View. If our View's SuperView has
         // a SuperView, keep going...
         // a SuperView, keep going...
-        return ret;
+        return new (new (parent.X + Frame.X, parent.Y + Frame.Y), Frame.Size);
     }
     }
 
 
     /// <summary>Does nothing for Adornment</summary>
     /// <summary>Does nothing for Adornment</summary>

+ 29 - 13
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -545,28 +545,49 @@ public partial class View
         }
         }
     }
     }
 
 
-    #nullable enable
+#nullable enable
     /// <summary>Finds which view that belong to the <paramref name="start"/> superview at the provided location.</summary>
     /// <summary>Finds which view that belong to the <paramref name="start"/> superview at the provided location.</summary>
     /// <param name="start">The superview where to look for.</param>
     /// <param name="start">The superview where to look for.</param>
     /// <param name="x">The column location in the superview.</param>
     /// <param name="x">The column location in the superview.</param>
     /// <param name="y">The row location in the superview.</param>
     /// <param name="y">The row location in the superview.</param>
-    /// <param name="resultX">The found view screen relative column location.</param>
-    /// <param name="resultY">The found view screen relative row location.</param>
+    /// <param name="findAdornments">TODO: Remove this in PR #3273</param>
     /// <returns>
     /// <returns>
     ///     The view that was found at the <paramref name="x"/> and <paramref name="y"/> coordinates.
     ///     The view that was found at the <paramref name="x"/> and <paramref name="y"/> coordinates.
     ///     <see langword="null"/> if no view was found.
     ///     <see langword="null"/> if no view was found.
     /// </returns>
     /// </returns>
+
     // CONCURRENCY: This method is not thread-safe.
     // CONCURRENCY: This method is not thread-safe.
     // Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
     // Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
-    public static View? FindDeepestView (View? start, int x, int y, out int resultX, out int resultY)
+    public static View? FindDeepestView (View? start, int x, int y, bool findAdornments = false)
     {
     {
-        resultY = resultX = 0;
+        if (start is null || !start.Visible)
+        {
+            return null;
+        }
 
 
-        if (start is null || !start.Frame.Contains (x, y))
+        if (!start.Frame.Contains (x, y))
         {
         {
             return null;
             return null;
         }
         }
 
 
+        if (findAdornments)
+        {
+            // TODO: This is a temporary hack for PR #3273; it is not actually used anywhere but unit tests at this point.
+            if (start.Margin.Thickness.Contains (start.Margin.Frame, x, y))
+            {
+                return start.Margin;
+            }
+            if (start.Border.Thickness.Contains (start.Border.Frame, x, y))
+            {
+                return start.Border;
+            }
+            if (start.Padding.Thickness.Contains (start.Padding.Frame, x, y))
+            {
+                return start.Padding;
+            }
+
+        }
+
         if (start.InternalSubviews is { Count: > 0 })
         if (start.InternalSubviews is { Count: > 0 })
         {
         {
             Point boundsOffset = start.GetBoundsOffset ();
             Point boundsOffset = start.GetBoundsOffset ();
@@ -579,19 +600,14 @@ public partial class View
 
 
                 if (v.Visible && v.Frame.Contains (rx, ry))
                 if (v.Visible && v.Frame.Contains (rx, ry))
                 {
                 {
-                    View? deep = FindDeepestView (v, rx, ry, out resultX, out resultY);
-
+                    View? deep = FindDeepestView (v, rx, ry, findAdornments);
                     return deep ?? v;
                     return deep ?? v;
                 }
                 }
             }
             }
         }
         }
-
-        resultX = x - start.Frame.X;
-        resultY = y - start.Frame.Y;
-
         return start;
         return start;
     }
     }
-    #nullable restore
+#nullable restore
 
 
     /// <summary>Gets the <see cref="Frame"/> with a screen-relative location.</summary>
     /// <summary>Gets the <see cref="Frame"/> with a screen-relative location.</summary>
     /// <returns>The location and size of the view in screen-relative coordinates.</returns>
     /// <returns>The location and size of the view in screen-relative coordinates.</returns>

+ 8 - 8
Terminal.Gui/Views/Menu/Menu.cs

@@ -727,13 +727,7 @@ internal sealed class Menu : View
             locationOffset.Y += SuperView.Border.Thickness.Top;
             locationOffset.Y += SuperView.Border.Thickness.Top;
         }
         }
 
 
-        View view = FindDeepestView (
-                                     this,
-                                     a.MouseEvent.X + locationOffset.X,
-                                     a.MouseEvent.Y + locationOffset.Y,
-                                     out int rx,
-                                     out int ry
-                                    );
+        View view = FindDeepestView (this, a.MouseEvent.X + locationOffset.X, a.MouseEvent.Y + locationOffset.Y);
 
 
         if (view == this)
         if (view == this)
         {
         {
@@ -742,7 +736,13 @@ internal sealed class Menu : View
                 throw new InvalidOperationException ("This shouldn't running on a invisible menu!");
                 throw new InvalidOperationException ("This shouldn't running on a invisible menu!");
             }
             }
 
 
-            var nme = new MouseEvent { X = rx, Y = ry, Flags = a.MouseEvent.Flags, View = view };
+            var screen = view.FrameToScreen ();
+            var nme = new MouseEvent {
+                X = a.MouseEvent.X - screen.X,
+                Y = a.MouseEvent.Y - screen.Y,
+                Flags = a.MouseEvent.Flags,
+                View = view
+            };
 
 
             if (MouseEvent (nme) || a.MouseEvent.Flags == MouseFlags.Button1Pressed || a.MouseEvent.Flags == MouseFlags.Button1Released)
             if (MouseEvent (nme) || a.MouseEvent.Flags == MouseFlags.Button1Pressed || a.MouseEvent.Flags == MouseFlags.Button1Released)
             {
             {

+ 168 - 0
UnitTests/View/Adornment/AdornmentTests.cs

@@ -7,6 +7,46 @@ public class AdornmentTests
     private readonly ITestOutputHelper _output;
     private readonly ITestOutputHelper _output;
     public AdornmentTests (ITestOutputHelper output) { _output = output; }
     public AdornmentTests (ITestOutputHelper output) { _output = output; }
 
 
+    [Fact]
+    public void Bounds_Location_Always_Empty_Size_Correct ()
+    {
+        var view = new View
+        {
+            X = 1,
+            Y = 2,
+            Width = 20,
+            Height = 31
+        };
+
+        var marginThickness = 1;
+        view.Margin.Thickness = new Thickness (marginThickness);
+
+        var borderThickness = 2;
+        view.Border.Thickness = new Thickness (borderThickness);
+
+        var paddingThickness = 3;
+        view.Padding.Thickness = new Thickness (paddingThickness);
+
+        view.BeginInit ();
+        view.EndInit ();
+
+        Assert.Equal (new Rectangle (1, 2, 20, 31), view.Frame);
+        Assert.Equal (new Rectangle (0, 0, 8, 19), view.Bounds);
+
+        Assert.Equal (new Rectangle (0, 0, view.Margin.Frame.Width - marginThickness * 2, view.Margin.Frame.Height - marginThickness * 2), view.Margin.Bounds);
+
+        Assert.Equal (new Rectangle (0, 0, view.Border.Frame.Width - borderThickness * 2, view.Border.Frame.Height - borderThickness * 2), view.Border.Bounds);
+
+        Assert.Equal (
+                      new Rectangle (
+                                     0,
+                                     0,
+                                     view.Padding.Frame.Width - (marginThickness + borderThickness) * 2,
+                                     view.Padding.Frame.Height - (marginThickness + borderThickness) * 2),
+                      view.Padding.Bounds);
+    }
+
+    // Test that Adornment.Bounds_get override uses Parent not SuperView
     [Fact]
     [Fact]
     public void BoundsToScreen_Uses_Parent_Not_SuperView ()
     public void BoundsToScreen_Uses_Parent_Not_SuperView ()
     {
     {
@@ -25,6 +65,134 @@ public class AdornmentTests
         Assert.Equal (new Rectangle (2, 4, 5, 5), boundsAsScreen);
         Assert.Equal (new Rectangle (2, 4, 5, 5), boundsAsScreen);
     }
     }
 
 
+    [Fact]
+    public void Frames_are_Parent_SuperView_Relative ()
+    {
+        var view = new View
+        {
+            X = 1,
+            Y = 2,
+            Width = 20,
+            Height = 31
+        };
+
+        var marginThickness = 1;
+        view.Margin.Thickness = new Thickness (marginThickness);
+
+        var borderThickness = 2;
+        view.Border.Thickness = new Thickness (borderThickness);
+
+        var paddingThickness = 3;
+        view.Padding.Thickness = new Thickness (paddingThickness);
+
+        view.BeginInit ();
+        view.EndInit ();
+
+        Assert.Equal (new Rectangle (1, 2, 20, 31), view.Frame);
+        Assert.Equal (new Rectangle (0, 0, 8, 19), view.Bounds);
+
+        // Margin.Frame is always the same as the view frame
+        Assert.Equal (new Rectangle (0, 0, 20, 31), view.Margin.Frame);
+
+        // Border.Frame is View.Frame minus the Margin thickness 
+        Assert.Equal (
+                      new Rectangle (marginThickness, marginThickness, view.Frame.Width - marginThickness * 2, view.Frame.Height - marginThickness * 2),
+                      view.Border.Frame);
+
+        // Padding.Frame is View.Frame minus the Border thickness plus Margin thickness
+        Assert.Equal (
+                      new Rectangle (
+                                     marginThickness + borderThickness,
+                                     marginThickness + borderThickness,
+                                     view.Frame.Width - (marginThickness + borderThickness) * 2,
+                                     view.Frame.Height - (marginThickness + borderThickness) * 2),
+                      view.Padding.Frame);
+    }
+
+    // Test that Adornment.FrameToScreen override retains Frame.Size
+    [Theory]
+    [InlineData (0, 0, 0)]
+    [InlineData (0, 1, 1)]
+    [InlineData (0, 10, 10)]
+    [InlineData (1, 0, 0)]
+    [InlineData (1, 1, 1)]
+    [InlineData (1, 10, 10)]
+    public void FrameToScreen_Retains_Frame_Size (int marginThickness, int w, int h)
+    {
+        var parent = new View { X = 1, Y = 2, Width = w, Height = h };
+        parent.Margin.Thickness = new Thickness (marginThickness);
+
+        parent.BeginInit ();
+        parent.EndInit ();
+
+        Assert.Equal (new Rectangle (1, 2, w, h), parent.Frame);
+        Assert.Equal (new Rectangle (0, 0, w, h), parent.Margin.Frame);
+
+        Assert.Equal (parent.Frame, parent.Margin.FrameToScreen ());
+    }
+
+    // Test that Adornment.FrameToScreen override returns Frame if Parent is null
+    [Fact]
+    public void FrameToScreen_Returns_Frame_If_Parent_Is_Null ()
+    {
+        var a = new Adornment
+        {
+            X = 1,
+            Y = 2,
+            Width = 3,
+            Height = 4
+        };
+
+        Assert.Null (a.Parent);
+        Assert.Equal (a.Frame, a.FrameToScreen ());
+    }
+
+    // Test that Adornment.FrameToScreen override returns correct location
+    [Theory]
+    [InlineData (0, 0, 0, 0)]
+    [InlineData (0, 0, 1, 1)]
+    [InlineData (0, 0, 10, 10)]
+    [InlineData (1, 0, 0, 0)]
+    [InlineData (1, 0, 1, 1)]
+    [InlineData (1, 0, 10, 10)]
+    [InlineData (0, 1, 0, 0)]
+    [InlineData (0, 1, 1, 1)]
+    [InlineData (0, 1, 10, 10)]
+    [InlineData (1, 1, 0, 0)]
+    [InlineData (1, 1, 1, 1)]
+    [InlineData (1, 1, 10, 10)]
+    public void FrameToScreen_Returns_Screen_Location (int marginThickness, int borderThickness, int x, int y)
+    {
+        var superView = new View
+        {
+            X = 1,
+            Y = 1,
+            Width = 20,
+            Height = 20
+        };
+        superView.Margin.Thickness = new Thickness (marginThickness);
+        superView.Border.Thickness = new Thickness (borderThickness);
+
+        var view = new View { X = x, Y = y, Width = 1, Height = 1 };
+        superView.Add (view);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        Assert.Equal (new Rectangle (x, y, 1, 1), view.Frame);
+        Assert.Equal (new Rectangle (0, 0, 20, 20), superView.Margin.Frame);
+
+        Assert.Equal (
+                      new Rectangle (marginThickness, marginThickness, 20 - marginThickness * 2, 20 - marginThickness * 2),
+                      superView.Border.Frame
+                     );
+
+        Assert.Equal (
+                      new Rectangle (superView.Frame.X + marginThickness, superView.Frame.Y + marginThickness, 20 - marginThickness * 2, 20 - marginThickness * 2),
+                      superView.Border.FrameToScreen ()
+                     );
+    }
+
+    // Test that Adornment.FrameToScreen override uses Parent not SuperView
     [Fact]
     [Fact]
     public void FrameToScreen_Uses_Parent_Not_SuperView ()
     public void FrameToScreen_Uses_Parent_Not_SuperView ()
     {
     {

+ 285 - 0
UnitTests/View/FindDeepestViewTests.cs

@@ -0,0 +1,285 @@
+using UICatalog.Scenarios;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+/// <summary>
+/// Tests View.FindDeepestView
+/// </summary>
+/// <param name="output"></param>
+public class FindDeepestViewTests (ITestOutputHelper output)
+{
+    // Test that FindDeepestView returns the correct view if the start view has no subviews
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (1, 1)]
+    [InlineData (2, 2)]
+    public void Returns_Start_If_No_SubViews (int testX, int testY)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+
+        Assert.Same (start, View.FindDeepestView (start, testX, testY));
+    }
+    
+    // Test that FindDeepestView returns null if the start view has no subviews and coords are outside the view
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (2, 1)]
+    [InlineData (20, 20)]
+    public void Returns_Null_If_No_SubViews_Coords_Outside (int testX, int testY)
+    {
+        var start = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 10, Height = 10,
+        };
+
+        Assert.Null(View.FindDeepestView (start, testX, testY));
+    }
+
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (2, 1)]
+    [InlineData (20, 20)]
+    public void Returns_Null_If_Start_Not_Visible (int testX, int testY)
+    {
+        var start = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 10, Height = 10,
+            Visible = false,
+        };
+
+        Assert.Null (View.FindDeepestView (start, testX, testY));
+    }
+
+    // Test that FindDeepestView returns the correct view if the start view has subviews
+    [Theory]
+    [InlineData (0, 0, false)]
+    [InlineData (1, 1, false)]
+    [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)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+
+        var subview = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 5, Height = 5,
+        };
+        start.Add (subview);
+
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == subview);
+    }
+
+    [Theory]
+    [InlineData (0, 0, false)]
+    [InlineData (1, 1, false)]
+    [InlineData (9, 9, false)]
+    [InlineData (10, 10, false)]
+    [InlineData (6, 7, false)]
+    [InlineData (1, 2, false)]
+    [InlineData (5, 6, false)]
+    public void Returns_Null_If_SubView_NotVisible (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+
+        var subview = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 5, Height = 5,
+            Visible = false
+        };
+        start.Add (subview);
+
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == subview);
+    }
+
+
+    [Theory]
+    [InlineData (0, 0, false)]
+    [InlineData (1, 1, false)]
+    [InlineData (9, 9, false)]
+    [InlineData (10, 10, false)]
+    [InlineData (6, 7, false)]
+    [InlineData (1, 2, false)]
+    [InlineData (5, 6, false)]
+    public void Returns_Null_If_Not_Visible_And_SubView_Visible (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+            Visible = false
+        };
+
+        var subview = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 5, Height = 5,
+        };
+        start.Add (subview);
+        subview.Visible = true;
+        Assert.True (subview.Visible);
+        Assert.False (start.Visible);
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == subview);
+    }
+    
+    // Test that FindDeepestView works if the start view has positive Adornments
+    [Theory]
+    [InlineData (0, 0, false)]
+    [InlineData (1, 1, false)]
+    [InlineData (9, 9, false)]
+    [InlineData (10, 10, false)]
+    [InlineData (7, 8, false)]
+    [InlineData (1, 2, false)]
+
+    [InlineData (2, 3, true)]
+    [InlineData (5, 6, true)]
+    [InlineData (2, 3, true)]
+    [InlineData (6, 7, true)]
+    public void Returns_Correct_If_Start_Has_Adornments (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+        start.Margin.Thickness = new Thickness (1);
+
+        var subview = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 5, Height = 5,
+        };
+        start.Add (subview);
+
+        var found = View.FindDeepestView (start, testX, testY, true);
+
+        Assert.Equal (expectedSubViewFound, found == subview);
+    }
+
+    [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 (View))]
+    public void Returns_Adornment_If_Start_Has_Adornments (int testX, int testY, Type expectedAdornmentType)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+        start.Margin.Thickness = new Thickness (1);
+        start.Border.Thickness = new Thickness (1);
+        start.Padding.Thickness = new Thickness (1);
+
+        var subview = new View ()
+        {
+            X = 1, Y = 1,
+            Width = 1, Height = 1,
+        };
+        start.Add (subview);
+
+        var found = View.FindDeepestView (start, testX, testY, true);
+        Assert.Equal(expectedAdornmentType, found.GetType());
+    }
+
+    // Test that FindDeepestView works if the subview has positive Adornments
+    [Theory]
+    [InlineData (0, 0, false)]
+    [InlineData (1, 1, false)]
+    [InlineData (9, 9, false)]
+    [InlineData (10, 10, false)]
+    [InlineData (7, 8, false)]
+    [InlineData (6, 7, false)]
+
+    [InlineData (1, 2, true)]
+    [InlineData (2, 3, true)]
+    [InlineData (5, 6, true)]
+    [InlineData (2, 3, true)]
+    public void Returns_Correct_If_SubView_Has_Adornments (int testX, int testY, bool expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10,
+        };
+
+        var subview = new View ()
+        {
+            X = 1, Y = 2,
+            Width = 5, Height = 5,
+        };
+        subview.Margin.Thickness = new Thickness (1);
+        start.Add (subview);
+
+        var found = View.FindDeepestView (start, testX, testY);
+
+        Assert.Equal (expectedSubViewFound, found == subview);
+    }
+
+    // Test that FindDeepestView works with nested subviews
+    [Theory]
+    [InlineData (0, 0, -1)]
+    [InlineData (9, 9, -1)]
+    [InlineData (10, 10, -1)]
+
+    [InlineData (1, 1, 0)]
+    [InlineData (1, 2, 0)]
+    [InlineData (2, 2, 1)]
+    [InlineData (3, 3, 2)]
+    [InlineData (5, 5, 2)]
+    public void Returns_Correct_With_NestedSubViews (int testX, int testY, int expectedSubViewFound)
+    {
+        var start = new View ()
+        {
+            Width = 10, Height = 10
+        };
+
+        int numSubViews = 3;
+        List<View> subviews = new List<View> ();
+        for (int i = 0; i < numSubViews; i++)
+        {
+            var subview = new View ()
+            {
+                X = 1, Y = 1,
+                Width = 5, Height = 5,
+            };
+            subviews.Add (subview);
+
+            if (i > 0)
+            {
+                subviews [i - 1].Add (subview);
+            }
+        }
+
+        start.Add (subviews [0]);
+
+        var found = View.FindDeepestView (start, testX, testY);
+        Assert.Equal (expectedSubViewFound, subviews.IndexOf(found));
+    }
+}

+ 2 - 1
UnitTests/View/Layout/LayoutTests.cs

@@ -1,4 +1,5 @@
 using Xunit.Abstractions;
 using Xunit.Abstractions;
+using static Unix.Terminal.Curses;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 
 
@@ -549,7 +550,7 @@ public class LayoutTests
         Assert.Equal (new Rectangle (0, 2, 10, 3), win2.Frame);
         Assert.Equal (new Rectangle (0, 2, 10, 3), win2.Frame);
         Assert.Equal (new Rectangle (0, 0, 8, 1), view2.Frame);
         Assert.Equal (new Rectangle (0, 0, 8, 1), view2.Frame);
         Assert.Equal (new Rectangle (0, 0, 7, 1), view3.Frame);
         Assert.Equal (new Rectangle (0, 0, 7, 1), view3.Frame);
-        var foundView = View.FindDeepestView (top, 9, 4, out int rx, out int ry);
+        var foundView = View.FindDeepestView (top, 9, 4);
         Assert.Equal (foundView, view2);
         Assert.Equal (foundView, view2);
 
 
         Application.OnMouseEvent (
         Application.OnMouseEvent (

+ 72 - 60
UnitTests/View/NavigationTests.cs

@@ -856,39 +856,46 @@ public class NavigationTests
         top.BoundsToScreen (-1, -1, out col, out row);
         top.BoundsToScreen (-1, -1, out col, out row);
         Assert.Equal (0, col);
         Assert.Equal (0, col);
         Assert.Equal (0, row);
         Assert.Equal (0, row);
-        Assert.Equal (top, View.FindDeepestView (top, 0, 0, out int rx, out int ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        var found = View.FindDeepestView (top, 0, 0);
+        Assert.Equal (top, found);
+ 
+        Assert.Equal (0, found.Frame.X);
+        Assert.Equal (0, found.Frame.Y);
         Assert.Equal (new Point (3, 2), top.ScreenToFrame (3, 2));
         Assert.Equal (new Point (3, 2), top.ScreenToFrame (3, 2));
         top.BoundsToScreen (3, 2, out col, out row);
         top.BoundsToScreen (3, 2, out col, out row);
         Assert.Equal (4, col);
         Assert.Equal (4, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (view, View.FindDeepestView (top, col, row, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
-        Assert.Equal (top, View.FindDeepestView (top, 3, 2, out rx, out ry));
-        Assert.Equal (3, rx);
-        Assert.Equal (2, ry);
+        found = View.FindDeepestView (top, col, row);
+        Assert.Equal (view, found);
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
+        found = View.FindDeepestView (top, 3, 2);
+        Assert.Equal (top, found);
+        //Assert.Equal (3, found.FrameToScreen ().X);
+        //Assert.Equal (2, found.FrameToScreen ().Y);
         Assert.Equal (new Point (13, 2), top.ScreenToFrame (13, 2));
         Assert.Equal (new Point (13, 2), top.ScreenToFrame (13, 2));
         top.BoundsToScreen (12, 2, out col, out row);
         top.BoundsToScreen (12, 2, out col, out row);
         Assert.Equal (13, col);
         Assert.Equal (13, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (view, View.FindDeepestView (top, col, row, out rx, out ry));
-        Assert.Equal (9, rx);
-        Assert.Equal (0, ry);
+        found = View.FindDeepestView (top, col, row);
+        Assert.Equal (view, found);
+        //Assert.Equal (9, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         top.BoundsToScreen (13, 2, out col, out row);
         top.BoundsToScreen (13, 2, out col, out row);
         Assert.Equal (14, col);
         Assert.Equal (14, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (top, View.FindDeepestView (top, 13, 2, out rx, out ry));
-        Assert.Equal (13, rx);
-        Assert.Equal (2, ry);
+        found = View.FindDeepestView (top, 13, 2);
+        Assert.Equal (top, found);
+        //Assert.Equal (13, found.FrameToScreen ().X);
+        //Assert.Equal (2, found.FrameToScreen ().Y);
         Assert.Equal (new Point (14, 3), top.ScreenToFrame (14, 3));
         Assert.Equal (new Point (14, 3), top.ScreenToFrame (14, 3));
         top.BoundsToScreen (14, 3, out col, out row);
         top.BoundsToScreen (14, 3, out col, out row);
         Assert.Equal (15, col);
         Assert.Equal (15, col);
         Assert.Equal (4, row);
         Assert.Equal (4, row);
-        Assert.Equal (top, View.FindDeepestView (top, 14, 3, out rx, out ry));
-        Assert.Equal (14, rx);
-        Assert.Equal (3, ry);
+        found = View.FindDeepestView (top, 14, 3);
+        Assert.Equal (top, found);
+        //Assert.Equal (14, found.FrameToScreen ().X);
+        //Assert.Equal (3, found.FrameToScreen ().Y);
 
 
         // view
         // view
         Assert.Equal (new Point (-4, -3), view.ScreenToFrame (0, 0));
         Assert.Equal (new Point (-4, -3), view.ScreenToFrame (0, 0));
@@ -907,30 +914,34 @@ public class NavigationTests
         view.BoundsToScreen (-4, -3, out col, out row);
         view.BoundsToScreen (-4, -3, out col, out row);
         Assert.Equal (0, col);
         Assert.Equal (0, col);
         Assert.Equal (0, row);
         Assert.Equal (0, row);
-        Assert.Equal (top, View.FindDeepestView (top, 0, 0, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        found = View.FindDeepestView (top, 0, 0);
+        Assert.Equal (top, found);
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (-1, -1), view.ScreenToFrame (3, 2));
         Assert.Equal (new Point (-1, -1), view.ScreenToFrame (3, 2));
         view.BoundsToScreen (0, 0, out col, out row);
         view.BoundsToScreen (0, 0, out col, out row);
         Assert.Equal (4, col);
         Assert.Equal (4, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (view, View.FindDeepestView (top, 4, 3, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        found = View.FindDeepestView (top, 4, 3);
+        Assert.Equal (view, found);
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (9, -1), view.ScreenToFrame (13, 2));
         Assert.Equal (new Point (9, -1), view.ScreenToFrame (13, 2));
         view.BoundsToScreen (10, 0, out col, out row);
         view.BoundsToScreen (10, 0, out col, out row);
         Assert.Equal (14, col);
         Assert.Equal (14, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (top, View.FindDeepestView (top, 14, 3, out rx, out ry));
-        Assert.Equal (14, rx);
-        Assert.Equal (3, ry);
+        found = View.FindDeepestView (top, 14, 3);
+        Assert.Equal (top, found);
+        //Assert.Equal (14, found.FrameToScreen ().X);
+        //Assert.Equal (3, found.FrameToScreen ().Y);
         Assert.Equal (new Point (10, 0), view.ScreenToFrame (14, 3));
         Assert.Equal (new Point (10, 0), view.ScreenToFrame (14, 3));
         view.BoundsToScreen (11, 1, out col, out row);
         view.BoundsToScreen (11, 1, out col, out row);
         Assert.Equal (15, col);
         Assert.Equal (15, col);
         Assert.Equal (4, row);
         Assert.Equal (4, row);
-        Assert.Equal (top, View.FindDeepestView (top, 15, 4, out rx, out ry));
-        Assert.Equal (15, rx);
-        Assert.Equal (4, ry);
+        found = View.FindDeepestView (top, 15, 4);
+        Assert.Equal (top, found);
+        //Assert.Equal (15, found.FrameToScreen ().X);
+        //Assert.Equal (4, found.FrameToScreen ().Y);
     }
     }
 
 
     [Fact]
     [Fact]
@@ -1004,30 +1015,31 @@ public class NavigationTests
         top.BoundsToScreen (-4, -3, out col, out row);
         top.BoundsToScreen (-4, -3, out col, out row);
         Assert.Equal (0, col);
         Assert.Equal (0, col);
         Assert.Equal (0, row);
         Assert.Equal (0, row);
-        Assert.Null (View.FindDeepestView (top, -4, -3, out int rx, out int ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        var found = View.FindDeepestView (top, -4, -3);
+        Assert.Null (found);
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (Point.Empty, top.ScreenToFrame (3, 2));
         Assert.Equal (Point.Empty, top.ScreenToFrame (3, 2));
         top.BoundsToScreen (0, 0, out col, out row);
         top.BoundsToScreen (0, 0, out col, out row);
         Assert.Equal (4, col);
         Assert.Equal (4, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (top, View.FindDeepestView (top, 3, 2, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        Assert.Equal (top, View.FindDeepestView (top, 3, 2));
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (10, 0), top.ScreenToFrame (13, 2));
         Assert.Equal (new Point (10, 0), top.ScreenToFrame (13, 2));
         top.BoundsToScreen (10, 0, out col, out row);
         top.BoundsToScreen (10, 0, out col, out row);
         Assert.Equal (14, col);
         Assert.Equal (14, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (top, View.FindDeepestView (top, 13, 2, out rx, out ry));
-        Assert.Equal (10, rx);
-        Assert.Equal (0, ry);
+        Assert.Equal (top, View.FindDeepestView (top, 13, 2));
+        //Assert.Equal (10, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (11, 1), top.ScreenToFrame (14, 3));
         Assert.Equal (new Point (11, 1), top.ScreenToFrame (14, 3));
         top.BoundsToScreen (11, 1, out col, out row);
         top.BoundsToScreen (11, 1, out col, out row);
         Assert.Equal (15, col);
         Assert.Equal (15, col);
         Assert.Equal (4, row);
         Assert.Equal (4, row);
-        Assert.Equal (top, View.FindDeepestView (top, 14, 3, out rx, out ry));
-        Assert.Equal (11, rx);
-        Assert.Equal (1, ry);
+        Assert.Equal (top, View.FindDeepestView (top, 14, 3));
+        //Assert.Equal (11, found.FrameToScreen ().X);
+        //Assert.Equal (1, found.FrameToScreen ().Y);
 
 
         // view
         // view
         Assert.Equal (new Point (-7, -5), view.ScreenToFrame (0, 0));
         Assert.Equal (new Point (-7, -5), view.ScreenToFrame (0, 0));
@@ -1043,44 +1055,44 @@ public class NavigationTests
         view.BoundsToScreen (-6, -4, out col, out row);
         view.BoundsToScreen (-6, -4, out col, out row);
         Assert.Equal (1, col);
         Assert.Equal (1, col);
         Assert.Equal (1, row);
         Assert.Equal (1, row);
-        Assert.Null (View.FindDeepestView (top, 1, 1, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        Assert.Null (View.FindDeepestView (top, 1, 1));
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (-4, -3), view.ScreenToFrame (3, 2));
         Assert.Equal (new Point (-4, -3), view.ScreenToFrame (3, 2));
         view.BoundsToScreen (-3, -2, out col, out row);
         view.BoundsToScreen (-3, -2, out col, out row);
         Assert.Equal (4, col);
         Assert.Equal (4, col);
         Assert.Equal (3, row);
         Assert.Equal (3, row);
-        Assert.Equal (top, View.FindDeepestView (top, 4, 3, out rx, out ry));
-        Assert.Equal (1, rx);
-        Assert.Equal (1, ry);
+        Assert.Equal (top, View.FindDeepestView (top, 4, 3));
+        //Assert.Equal (1, found.FrameToScreen ().X);
+        //Assert.Equal (1, found.FrameToScreen ().Y);
         Assert.Equal (new Point (-1, -1), view.ScreenToFrame (6, 4));
         Assert.Equal (new Point (-1, -1), view.ScreenToFrame (6, 4));
         view.BoundsToScreen (0, 0, out col, out row);
         view.BoundsToScreen (0, 0, out col, out row);
         Assert.Equal (7, col);
         Assert.Equal (7, col);
         Assert.Equal (5, row);
         Assert.Equal (5, row);
-        Assert.Equal (view, View.FindDeepestView (top, 7, 5, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        Assert.Equal (view, View.FindDeepestView (top, 7, 5));
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (6, -1), view.ScreenToFrame (13, 4));
         Assert.Equal (new Point (6, -1), view.ScreenToFrame (13, 4));
         view.BoundsToScreen (7, 0, out col, out row);
         view.BoundsToScreen (7, 0, out col, out row);
         Assert.Equal (14, col);
         Assert.Equal (14, col);
         Assert.Equal (5, row);
         Assert.Equal (5, row);
-        Assert.Equal (view, View.FindDeepestView (top, 14, 5, out rx, out ry));
-        Assert.Equal (7, rx);
-        Assert.Equal (0, ry);
+        Assert.Equal (view, View.FindDeepestView (top, 14, 5));
+        //Assert.Equal (7, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
         Assert.Equal (new Point (7, -2), view.ScreenToFrame (14, 3));
         Assert.Equal (new Point (7, -2), view.ScreenToFrame (14, 3));
         view.BoundsToScreen (8, -1, out col, out row);
         view.BoundsToScreen (8, -1, out col, out row);
         Assert.Equal (15, col);
         Assert.Equal (15, col);
         Assert.Equal (4, row);
         Assert.Equal (4, row);
-        Assert.Equal (top, View.FindDeepestView (top, 15, 4, out rx, out ry));
-        Assert.Equal (12, rx);
-        Assert.Equal (2, ry);
+        Assert.Equal (top, View.FindDeepestView (top, 15, 4));
+        //Assert.Equal (12, found.FrameToScreen ().X);
+        //Assert.Equal (2, found.FrameToScreen ().Y);
         Assert.Equal (new Point (16, -2), view.ScreenToFrame (23, 3));
         Assert.Equal (new Point (16, -2), view.ScreenToFrame (23, 3));
         view.BoundsToScreen (17, -1, out col, out row);
         view.BoundsToScreen (17, -1, out col, out row);
         Assert.Equal (24, col);
         Assert.Equal (24, col);
         Assert.Equal (4, row);
         Assert.Equal (4, row);
-        Assert.Null (View.FindDeepestView (top, 24, 4, out rx, out ry));
-        Assert.Equal (0, rx);
-        Assert.Equal (0, ry);
+        Assert.Null (View.FindDeepestView (top, 24, 4));
+        //Assert.Equal (0, found.FrameToScreen ().X);
+        //Assert.Equal (0, found.FrameToScreen ().Y);
     }
     }
 
 
     [Fact]
     [Fact]

+ 2 - 2
UnitTests/Views/MenuBarTests.cs

@@ -518,7 +518,7 @@ public class MenuBarTests
 
 
         Application.OnMouseEvent (
         Application.OnMouseEvent (
                                   new MouseEventEventArgs (
                                   new MouseEventEventArgs (
-                                                           new MouseEvent { X = 20, Y = 4, Flags = MouseFlags.Button1Clicked }
+                                                           new MouseEvent { X = 20, Y = 5, Flags = MouseFlags.Button1Clicked }
                                                           )
                                                           )
                                  );
                                  );
 
 
@@ -555,7 +555,7 @@ public class MenuBarTests
 
 
             Application.OnMouseEvent (
             Application.OnMouseEvent (
                                       new MouseEventEventArgs (
                                       new MouseEventEventArgs (
-                                                               new MouseEvent { X = 20, Y = 4 + i, Flags = MouseFlags.Button1Clicked }
+                                                               new MouseEvent { X = 20, Y = 5 + i, Flags = MouseFlags.Button1Clicked }
                                                               )
                                                               )
                                      );
                                      );