Sfoglia il codice sorgente

ViewportToScreen now deals with + location. Updated unit tests.

Tig 1 anno fa
parent
commit
f55d9bbdcd

+ 14 - 20
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -102,7 +102,7 @@ public partial class View
             }
 
             Point viewportOffset = super.GetViewportOffset ();
-            viewportOffset.Offset(super.Frame.X, super.Frame.Y);
+            viewportOffset.Offset (super.Frame.X, super.Frame.Y);
             ret.X += viewportOffset.X;
             ret.Y += viewportOffset.Y;
             super = super.SuperView;
@@ -286,6 +286,8 @@ public partial class View
 
     #region Viewport
 
+    private Point _viewportOffset;
+
     /// <summary>
     ///     Gets or sets the rectangle describing the portion of the View's content that is visible to the user.
     ///     The viewport Location is relative to the top-left corner of the inner rectangle of the <see cref="Adornment"/>s.
@@ -324,30 +326,19 @@ public partial class View
             if (Margin is null || Border is null || Padding is null)
             {
                 // CreateAdornments has not been called yet.
-                return Rectangle.Empty with { Size = Frame.Size };
+                return new (_viewportOffset, Frame.Size);
             }
 
             Thickness totalThickness = GetAdornmentsThickness ();
 
-            return Rectangle.Empty with
-            {
-                Size = new (
-                            Math.Max (0, Frame.Size.Width - totalThickness.Horizontal),
-                            Math.Max (0, Frame.Size.Height - totalThickness.Vertical))
-            };
+            return new (_viewportOffset,
+                new (Math.Max (0, Frame.Size.Width - totalThickness.Horizontal),
+                     Math.Max (0, Frame.Size.Height - totalThickness.Vertical)));
         }
         set
         {
-            // TODO: Should we enforce Viewport.X/Y == 0? The code currently ignores value.X/Y which is
-            // TODO: correct behavior, but is silent. Perhaps an exception?
-#if DEBUG
-            if (value.Location != Point.Empty)
-            {
-                Debug.WriteLine (
-                                 $"WARNING: Viewport.Location must always be 0,0. Location ({value.Location}) is ignored. {this}"
-                                );
-            }
-#endif // DEBUG
+            _viewportOffset = value.Location;
+
             Thickness totalThickness = GetAdornmentsThickness ();
 
             Frame = Frame with
@@ -365,7 +356,7 @@ public partial class View
         // Translate bounds to Frame (our SuperView's Viewport-relative coordinates)
         Rectangle screen = FrameToScreen ();
         Point viewportOffset = GetViewportOffset ();
-        screen.Offset (viewportOffset.X + viewport.X, viewportOffset.Y + viewport.Y);
+        screen.Offset (viewportOffset.X + Viewport.X + viewport.X, viewportOffset.Y + Viewport.Y + viewport.Y);
 
         return new (screen.Location, viewport.Size);
     }
@@ -387,7 +378,10 @@ public partial class View
     ///     Helper to get the X and Y offset of the Viewport from the Frame. This is the sum of the Left and Top properties
     ///     of <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
     /// </summary>
-    public Point GetViewportOffset () { return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location; }
+    public Point GetViewportOffset ()
+    {
+        return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location;
+    }
 
     /// <summary>
     /// Gets or sets the size of the View's content. If the value is <c>Size.Empty</c> the size of the content is

+ 0 - 49
UnitTests/View/FrameTests.cs

@@ -1,49 +0,0 @@
-using Xunit.Abstractions;
-
-namespace Terminal.Gui.ViewTests;
-
-public class FrameTests
-{
-    private readonly ITestOutputHelper _output;
-    public FrameTests (ITestOutputHelper output) { _output = output; }
-
-    // Test FrameToScreen
-    [Theory]
-    [InlineData (0, 0, 0, 0)]
-    [InlineData (1, 0, 1, 0)]
-    [InlineData (0, 1, 0, 1)]
-    [InlineData (1, 1, 1, 1)]
-    [InlineData (10, 10, 10, 10)]
-    public void FrameToScreen_NoSuperView (int frameX, int frameY, int expectedScreenX, int expectedScreenY)
-    {
-        var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 };
-        var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10);
-        Rectangle actual = view.FrameToScreen ();
-        Assert.Equal (expected, actual);
-    }
-
-    [Theory]
-    [InlineData (0, 0, 0, 0, 0)]
-    [InlineData (1, 0, 0, 1, 1)]
-    [InlineData (2, 0, 0, 2, 2)]
-    [InlineData (1, 1, 0, 2, 1)]
-    [InlineData (1, 0, 1, 1, 2)]
-    [InlineData (1, 1, 1, 2, 2)]
-    [InlineData (1, 10, 10, 11, 11)]
-    public void FrameToScreen_SuperView (
-        int superOffset,
-        int frameX,
-        int frameY,
-        int expectedScreenX,
-        int expectedScreenY
-    )
-    {
-        var super = new View { X = superOffset, Y = superOffset, Width = 20, Height = 20 };
-
-        var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 };
-        super.Add (view);
-        var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10);
-        Rectangle actual = view.FrameToScreen ();
-        Assert.Equal (expected, actual);
-    }
-}

+ 0 - 142
UnitTests/View/Layout/AbsoluteLayoutTests.cs

@@ -11,80 +11,6 @@ public class AbsoluteLayoutTests
     private readonly ITestOutputHelper _output;
     public AbsoluteLayoutTests (ITestOutputHelper output) { _output = output; }
 
-    [Fact]
-    [TestRespondersDisposed]
-    public void AbsoluteLayout_Change_Frame ()
-    {
-        var frame = new Rectangle (1, 2, 3, 4);
-        var newFrame = new Rectangle (1, 2, 30, 40);
-
-        var v = new View ();
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        v.Dispose ();
-
-        v = new View { Frame = frame };
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-
-        v.Frame = newFrame;
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        Assert.Equal (newFrame, v.Frame);
-
-        Assert.Equal (
-                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
-                      v.Viewport
-                     ); // With Absolute Viewport *is* deterministic before Layout
-        Assert.Equal (Pos.At (1), v.X);
-        Assert.Equal (Pos.At (2), v.Y);
-        Assert.Equal (Dim.Sized (30), v.Width);
-        Assert.Equal (Dim.Sized (40), v.Height);
-        v.Dispose ();
-
-        v = new View { X = frame.X, Y = frame.Y, Text = "v" };
-        v.Frame = newFrame;
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        Assert.Equal (newFrame, v.Frame);
-
-        Assert.Equal (
-                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
-                      v.Viewport
-                     ); // With Absolute Viewport *is* deterministic before Layout
-        Assert.Equal (Pos.At (1), v.X);
-        Assert.Equal (Pos.At (2), v.Y);
-        Assert.Equal (Dim.Sized (30), v.Width);
-        Assert.Equal (Dim.Sized (40), v.Height);
-        v.Dispose ();
-
-        newFrame = new Rectangle (10, 20, 30, 40);
-        v = new View { Frame = frame };
-        v.Frame = newFrame;
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        Assert.Equal (newFrame, v.Frame);
-
-        Assert.Equal (
-                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
-                      v.Viewport
-                     ); // With Absolute Viewport *is* deterministic before Layout
-        Assert.Equal (Pos.At (10), v.X);
-        Assert.Equal (Pos.At (20), v.Y);
-        Assert.Equal (Dim.Sized (30), v.Width);
-        Assert.Equal (Dim.Sized (40), v.Height);
-        v.Dispose ();
-
-        v = new View { X = frame.X, Y = frame.Y, Text = "v" };
-        v.Frame = newFrame;
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        Assert.Equal (newFrame, v.Frame);
-
-        Assert.Equal (
-                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
-                      v.Viewport
-                     ); // With Absolute Viewport *is* deterministic before Layout
-        Assert.Equal (Pos.At (10), v.X);
-        Assert.Equal (Pos.At (20), v.Y);
-        Assert.Equal (Dim.Sized (30), v.Width);
-        Assert.Equal (Dim.Sized (40), v.Height);
-        v.Dispose ();
-    }
 
     [Fact]
     [TestRespondersDisposed]
@@ -344,72 +270,4 @@ public class AbsoluteLayoutTests
         Assert.Equal (new Rectangle (10, 10, 10, 10), v2.Frame);
         super.Dispose ();
     }
-
-    [Fact]
-    public void AbsoluteLayout_Setting_Viewport_Location_NotEmpty ()
-    {
-        // TODO: Should we enforce Viewport.X/Y == 0? The code currently ignores value.X/Y which is
-        // TODO: correct behavior, but is silent. Perhaps an exception?
-        var frame = new Rectangle (1, 2, 3, 4);
-        var newBounds = new Rectangle (10, 20, 30, 40);
-        var view = new View { Frame = frame };
-        view.Viewport = newBounds;
-        Assert.Equal (new Rectangle (0, 0, 30, 40), view.Viewport);
-        Assert.Equal (new Rectangle (1, 2, 30, 40), view.Frame);
-    }
-
-    [Fact]
-    public void AbsoluteLayout_Setting_Viewport_Sets_Frame ()
-    {
-        var frame = new Rectangle (1, 2, 3, 4);
-        var newBounds = new Rectangle (0, 0, 30, 40);
-
-        var v = new View { Frame = frame };
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-
-        v.Viewport = newBounds;
-        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-        Assert.Equal (newBounds, v.Viewport);
-        Assert.Equal (new Rectangle (1, 2, newBounds.Width, newBounds.Height), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, newBounds.Width, newBounds.Height), v.Viewport);
-        Assert.Equal (Pos.At (1), v.X);
-        Assert.Equal (Pos.At (2), v.Y);
-        Assert.Equal (Dim.Sized (30), v.Width);
-        Assert.Equal (Dim.Sized (40), v.Height);
-
-        newBounds = new Rectangle (0, 0, 3, 4);
-        v.Viewport = newBounds;
-        Assert.Equal (newBounds, v.Viewport);
-        Assert.Equal (new Rectangle (1, 2, newBounds.Width, newBounds.Height), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, newBounds.Width, newBounds.Height), v.Viewport);
-        Assert.Equal (Pos.At (1), v.X);
-        Assert.Equal (Pos.At (2), v.Y);
-        Assert.Equal (Dim.Sized (3), v.Width);
-        Assert.Equal (Dim.Sized (4), v.Height);
-
-        v.BorderStyle = LineStyle.Single;
-
-        // Viewport should shrink
-        Assert.Equal (new Rectangle (0, 0, 1, 2), v.Viewport);
-
-        // Frame should not change
-        Assert.Equal (new Rectangle (1, 2, 3, 4), v.Frame);
-        Assert.Equal (Pos.At (1), v.X);
-        Assert.Equal (Pos.At (2), v.Y);
-        Assert.Equal (Dim.Sized (3), v.Width);
-        Assert.Equal (Dim.Sized (4), v.Height);
-
-        // Now set bounds bigger as before
-        newBounds = new Rectangle (0, 0, 3, 4);
-        v.Viewport = newBounds;
-        Assert.Equal (newBounds, v.Viewport);
-
-        // Frame grows because there's now a border
-        Assert.Equal (new Rectangle (1, 2, 5, 6), v.Frame);
-        Assert.Equal (new Rectangle (0, 0, newBounds.Width, newBounds.Height), v.Viewport);
-        Assert.Equal (Pos.At (1), v.X);
-        Assert.Equal (Pos.At (2), v.Y);
-        Assert.Equal (Dim.Sized (5), v.Width);
-        Assert.Equal (Dim.Sized (6), v.Height);
-    }
 }

+ 109 - 0
UnitTests/View/Layout/FrameTests.cs

@@ -0,0 +1,109 @@
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+public class FrameTests (ITestOutputHelper output)
+{
+    private readonly ITestOutputHelper _output = output;
+
+    [Fact]
+    public void Frame_Empty_Default ()
+    {
+        View view = new ();
+        Assert.Equal(Rectangle.Empty, view.Frame);
+
+        view.BeginInit();
+        view.EndInit();
+        Assert.Equal (Rectangle.Empty, view.Frame);
+    }
+
+    [Fact]
+    public void Frame_Set_Sets ()
+    {
+        Rectangle frame = new (1, 2, 3, 4);
+        View view = new ();
+        Assert.Equal (Rectangle.Empty, view.Frame);
+
+        view.BeginInit ();
+        view.EndInit ();
+        view.Frame = frame;
+        Assert.Equal (frame, view.Frame);
+    }
+
+    // Moved this test from AbsoluteLayoutTests
+    // TODO: Refactor as Theory
+    [Fact]
+    [TestRespondersDisposed]
+    public void Frame_Set ()
+    {
+        var frame = new Rectangle (1, 2, 3, 4);
+        var newFrame = new Rectangle (1, 2, 30, 40);
+
+        var v = new View ();
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        v.Dispose ();
+
+        v = new View { Frame = frame };
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+
+        Assert.Equal (
+                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+
+        v = new View { X = frame.X, Y = frame.Y, Text = "v" };
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+
+        Assert.Equal (
+                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+
+        newFrame = new Rectangle (10, 20, 30, 40);
+        v = new View { Frame = frame };
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+
+        Assert.Equal (
+                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
+        Assert.Equal (Pos.At (10), v.X);
+        Assert.Equal (Pos.At (20), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+
+        v = new View { X = frame.X, Y = frame.Y, Text = "v" };
+        v.Frame = newFrame;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newFrame, v.Frame);
+
+        Assert.Equal (
+                      new Rectangle (0, 0, newFrame.Width, newFrame.Height),
+                      v.Viewport
+                     ); // With Absolute Viewport *is* deterministic before Layout
+        Assert.Equal (Pos.At (10), v.X);
+        Assert.Equal (Pos.At (20), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+        v.Dispose ();
+    }
+}

+ 68 - 12
UnitTests/View/Layout/ToScreenTests.cs

@@ -11,6 +11,46 @@ public class ToScreenTests (ITestOutputHelper output)
 {
     private readonly ITestOutputHelper _output = output;
 
+
+    // Test FrameToScreen
+    [Theory]
+    [InlineData (0, 0, 0, 0)]
+    [InlineData (1, 0, 1, 0)]
+    [InlineData (0, 1, 0, 1)]
+    [InlineData (1, 1, 1, 1)]
+    [InlineData (10, 10, 10, 10)]
+    public void FrameToScreen_NoSuperView (int frameX, int frameY, int expectedScreenX, int expectedScreenY)
+    {
+        var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 };
+        var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10);
+        Rectangle actual = view.FrameToScreen ();
+        Assert.Equal (expected, actual);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0)]
+    [InlineData (1, 0, 0, 1, 1)]
+    [InlineData (2, 0, 0, 2, 2)]
+    [InlineData (1, 1, 0, 2, 1)]
+    [InlineData (1, 0, 1, 1, 2)]
+    [InlineData (1, 1, 1, 2, 2)]
+    [InlineData (1, 10, 10, 11, 11)]
+    public void FrameToScreen_SuperView (
+        int superOffset,
+        int frameX,
+        int frameY,
+        int expectedScreenX,
+        int expectedScreenY
+    )
+    {
+        var super = new View { X = superOffset, Y = superOffset, Width = 20, Height = 20 };
+
+        var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 };
+        super.Add (view);
+        var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10);
+        Rectangle actual = view.FrameToScreen ();
+        Assert.Equal (expected, actual);
+    }
     [Theory]
     [InlineData (0, 0)]
     [InlineData (1, 1)]
@@ -205,14 +245,30 @@ public class ToScreenTests (ITestOutputHelper output)
         Assert.Equal (expectedX, screen.X);
     }
 
+    // ViewportToScreen tests ----------------------
 
+    [Fact]
+    public void ViewportToScreen_With_Positive_Viewport_Location ()
+    {
+        View view = new ()
+        {
+            Width = 10,
+            Height = 10
+        };
+
+        Rectangle testRect = new Rectangle (0, 0, 1, 1);
+        Assert.Equal (new Point (0, 0), view.ViewportToScreen (testRect).Location);
+        view.Viewport = view.Viewport with { Location = new Point (1, 1) };
+        Assert.Equal (new Rectangle (1, 1, 10, 10), view.Viewport);
+        Assert.Equal (new Point (1, 1), view.ViewportToScreen (testRect).Location);
+    }
 
     [Theory]
     [InlineData (0, 0, 0)]
     [InlineData (1, 0, 1)]
     [InlineData (-1, 0, -1)]
     [InlineData (11, 0, 11)]
-    public void BoundsToScreen_NoSuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NoSuperView_WithoutAdornments (int frameX, int viewportX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -222,7 +278,7 @@ public class ToScreenTests (ITestOutputHelper output)
         view.Frame = frame;
 
         // Act
-        var screen = view.ViewportToScreen (new (boundsX, 0, 0, 0));
+        var screen = view.ViewportToScreen (new (viewportX, 0, 0, 0));
 
         // Assert
         Assert.Equal (expectedX, screen.X);
@@ -243,7 +299,7 @@ public class ToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 1)]
     [InlineData (-1, -1, -1)]
     [InlineData (11, -1, 11)]
-    public void BoundsToScreen_NoSuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NoSuperView_WithAdornments (int frameX, int viewportX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -254,7 +310,7 @@ public class ToScreenTests (ITestOutputHelper output)
         view.Frame = frame;
 
         // Act
-        var screen = view.ViewportToScreen (new (boundsX, 0, 0, 0));
+        var screen = view.ViewportToScreen (new (viewportX, 0, 0, 0));
 
         // Assert
         Assert.Equal (expectedX, screen.X);
@@ -275,7 +331,7 @@ public class ToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_SuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_SuperView_WithoutAdornments (int frameX, int viewportX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -296,7 +352,7 @@ public class ToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // Act
-        var screen = view.ViewportToScreen (new (boundsX, 0, 0, 0));
+        var screen = view.ViewportToScreen (new (viewportX, 0, 0, 0));
 
         // Assert
         Assert.Equal (expectedX, screen.X);
@@ -317,7 +373,7 @@ public class ToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 1)]
     [InlineData (-1, -1, -1)]
     [InlineData (11, -1, 11)]
-    public void BoundsToScreen_SuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_SuperView_WithAdornments (int frameX, int viewportX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -339,7 +395,7 @@ public class ToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // Act
-        var screen = view.ViewportToScreen (new (boundsX, 0, 0, 0));
+        var screen = view.ViewportToScreen (new (viewportX, 0, 0, 0));
 
         // Assert
         Assert.Equal (expectedX, screen.X);
@@ -360,7 +416,7 @@ public class ToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 0)]
     [InlineData (-1, -1, -2)]
     [InlineData (11, -1, 10)]
-    public void BoundsToScreen_NestedSuperView_WithoutAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NestedSuperView_WithoutAdornments (int frameX, int viewportX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -391,7 +447,7 @@ public class ToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // Act
-        var screen = view.ViewportToScreen (new (boundsX, 0, 0, 0));
+        var screen = view.ViewportToScreen (new (viewportX, 0, 0, 0));
 
         // Assert
         Assert.Equal (expectedX, screen.X);
@@ -412,7 +468,7 @@ public class ToScreenTests (ITestOutputHelper output)
     [InlineData (1, -1, 2)]
     [InlineData (-1, -1, 0)]
     [InlineData (11, -1, 12)]
-    public void BoundsToScreen_NestedSuperView_WithAdornments (int frameX, int boundsX, int expectedX)
+    public void ViewportToScreen_NestedSuperView_WithAdornments (int frameX, int viewportX, int expectedX)
     {
         // We test with only X because Y is equivalent. Height/Width are irrelevant.
         // Arrange
@@ -445,7 +501,7 @@ public class ToScreenTests (ITestOutputHelper output)
         superView.LayoutSubviews ();
 
         // Act
-        var screen = view.ViewportToScreen (new (boundsX, 0, 0, 0));
+        var screen = view.ViewportToScreen (new (viewportX, 0, 0, 0));
 
         // Assert
         Assert.Equal (expectedX, screen.X);

+ 108 - 1
UnitTests/View/Layout/ViewportTests.cs

@@ -150,6 +150,112 @@ public class ViewportTests (ITestOutputHelper output)
         Assert.Equal (expectedW, bounds.Width);
     }
 
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (1, 0)]
+    [InlineData (0, 1)]
+    [InlineData (-1, -1)]
+    public void Set_Viewport_Location_Preserves_Size_And_Frame (int xOffset, int yOffset)
+    {
+        View view = new ()
+        {
+            Width = 10,
+            Height = 10
+        };
+
+        Assert.Equal (new Rectangle (0, 0, 10, 10), view.Frame);
+
+        Rectangle testRect = new Rectangle (0, 0, 1, 1);
+        Assert.Equal (new Point (0, 0), view.ViewportToScreen (testRect).Location);
+        view.Viewport = view.Viewport with { Location = new Point (xOffset, yOffset) };
+        Assert.Equal (new Rectangle (xOffset, yOffset, 10, 10), view.Viewport);
+
+        Assert.Equal (new Rectangle (0, 0, 10, 10), view.Frame);
+    }
+
+    [Fact]
+    public void Set_Viewport_Changes_Frame ()
+    {
+        var frame = new Rectangle (1, 2, 3, 4);
+        var newViewport = new Rectangle (0, 0, 30, 40);
+
+        var v = new View { Frame = frame };
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+        v.Viewport = newViewport;
+        Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+        Assert.Equal (newViewport, v.Viewport);
+        Assert.Equal (new Rectangle (1, 2, newViewport.Width, newViewport.Height), v.Frame);
+        Assert.Equal (new Rectangle (0, 0, newViewport.Width, newViewport.Height), v.Viewport);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (30), v.Width);
+        Assert.Equal (Dim.Sized (40), v.Height);
+
+        newViewport = new Rectangle (0, 0, 3, 4);
+        v.Viewport = newViewport;
+        Assert.Equal (newViewport, v.Viewport);
+        Assert.Equal (new Rectangle (1, 2, newViewport.Width, newViewport.Height), v.Frame);
+        Assert.Equal (new Rectangle (0, 0, newViewport.Width, newViewport.Height), v.Viewport);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+
+        v.BorderStyle = LineStyle.Single;
+
+        // Viewport should shrink
+        Assert.Equal (new Rectangle (0, 0, 1, 2), v.Viewport);
+
+        // Frame should not change
+        Assert.Equal (new Rectangle (1, 2, 3, 4), v.Frame);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (3), v.Width);
+        Assert.Equal (Dim.Sized (4), v.Height);
+
+        // Now set bounds bigger as before
+        newViewport = new Rectangle (0, 0, 3, 4);
+        v.Viewport = newViewport;
+        Assert.Equal (newViewport, v.Viewport);
+
+        // Frame grows because there's now a border
+        Assert.Equal (new Rectangle (1, 2, 5, 6), v.Frame);
+        Assert.Equal (new Rectangle (0, 0, newViewport.Width, newViewport.Height), v.Viewport);
+        Assert.Equal (Pos.At (1), v.X);
+        Assert.Equal (Pos.At (2), v.Y);
+        Assert.Equal (Dim.Sized (5), v.Width);
+        Assert.Equal (Dim.Sized (6), v.Height);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0)]
+    [InlineData (1, 0, 0)]
+    [InlineData (-1, 0, 0)]
+    [InlineData (10, 0, 0)]
+    [InlineData (11, 0, 0)]
+
+    [InlineData (0, 1, 1)]
+    [InlineData (1, 1, 1)]
+    [InlineData (-1, 1, 1)]
+    [InlineData (10, 1, 1)]
+    [InlineData (11, 1, 1)]
+    public void GetViewportOffset_Returns_Offset_From_Frame (int frameX, int adornmentThickness, int expectedOffset)
+    {
+        View view = new ()
+        {
+            X = 1,
+            Y = 1,
+            Width = 10,
+            Height = 10
+        };
+        view.BeginInit ();
+        view.EndInit ();
+        view.Margin.Thickness = new (adornmentThickness);
+
+        Assert.Equal (expectedOffset, view.GetViewportOffset ().X);
+    }
+
     [Fact]
     public void ContentSize_Empty_ByDefault ()
     {
@@ -158,6 +264,7 @@ public class ViewportTests (ITestOutputHelper output)
             Width = 1,
             Height = 1
         };
-        Assert.Equal(Size.Empty, view.ContentSize);
+        Assert.Equal (Size.Empty, view.ContentSize);
     }
+
 }